dan sinclair | a8274b2 | 2020-09-21 18:49:01 +0000 | [diff] [blame] | 1 | // Copyright 2020 The Tint Authors. |
| 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | // you may not use this file except in compliance with the License. |
| 5 | // You may obtain a copy of the License at |
| 6 | // |
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 9 | // Unless required by applicable law or agreed to in writing, software |
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | // See the License for the specific language governing permissions and |
| 13 | // limitations under the License. |
| 14 | |
| 15 | #include <string> |
| 16 | |
| 17 | #include "gtest/gtest.h" |
| 18 | #include "spirv/unified1/spirv.h" |
| 19 | #include "spirv/unified1/spirv.hpp11" |
| 20 | #include "src/ast/assignment_statement.h" |
| 21 | #include "src/ast/function.h" |
| 22 | #include "src/ast/identifier_expression.h" |
| 23 | #include "src/ast/pipeline_stage.h" |
| 24 | #include "src/ast/stage_decoration.h" |
dan sinclair | a8274b2 | 2020-09-21 18:49:01 +0000 | [diff] [blame] | 25 | #include "src/ast/variable.h" |
| 26 | #include "src/ast/workgroup_decoration.h" |
dan sinclair | a8274b2 | 2020-09-21 18:49:01 +0000 | [diff] [blame] | 27 | #include "src/type_determiner.h" |
| 28 | #include "src/writer/spirv/builder.h" |
| 29 | #include "src/writer/spirv/spv_dump.h" |
dan sinclair | 196e097 | 2020-11-13 18:13:24 +0000 | [diff] [blame] | 30 | #include "src/writer/spirv/test_helper.h" |
dan sinclair | a8274b2 | 2020-09-21 18:49:01 +0000 | [diff] [blame] | 31 | |
| 32 | namespace tint { |
| 33 | namespace writer { |
| 34 | namespace spirv { |
| 35 | namespace { |
| 36 | |
dan sinclair | 196e097 | 2020-11-13 18:13:24 +0000 | [diff] [blame] | 37 | using BuilderTest = TestHelper; |
dan sinclair | a8274b2 | 2020-09-21 18:49:01 +0000 | [diff] [blame] | 38 | |
| 39 | TEST_F(BuilderTest, FunctionDecoration_Stage) { |
Ben Clayton | e6e7041 | 2021-01-05 15:29:29 +0000 | [diff] [blame] | 40 | auto* func = |
| 41 | Func("main", {}, ty.void_, ast::StatementList{}, |
| 42 | ast::FunctionDecorationList{ |
| 43 | create<ast::StageDecoration>(ast::PipelineStage::kVertex), |
| 44 | }); |
dan sinclair | a8274b2 | 2020-09-21 18:49:01 +0000 | [diff] [blame] | 45 | |
dan sinclair | b583993 | 2020-12-16 21:38:40 +0000 | [diff] [blame] | 46 | ASSERT_TRUE(b.GenerateFunction(func)) << b.error(); |
dan sinclair | 84f8275 | 2020-11-10 21:49:56 +0000 | [diff] [blame] | 47 | EXPECT_EQ(DumpInstructions(b.entry_points()), |
dan sinclair | 987376c | 2021-01-12 04:34:53 +0000 | [diff] [blame^] | 48 | R"(OpEntryPoint Vertex %3 "main" |
dan sinclair | a8274b2 | 2020-09-21 18:49:01 +0000 | [diff] [blame] | 49 | )"); |
| 50 | } |
| 51 | |
| 52 | struct FunctionStageData { |
| 53 | ast::PipelineStage stage; |
| 54 | SpvExecutionModel model; |
| 55 | }; |
| 56 | inline std::ostream& operator<<(std::ostream& out, FunctionStageData data) { |
| 57 | out << data.stage; |
| 58 | return out; |
| 59 | } |
dan sinclair | 196e097 | 2020-11-13 18:13:24 +0000 | [diff] [blame] | 60 | using FunctionDecoration_StageTest = TestParamHelper<FunctionStageData>; |
dan sinclair | a8274b2 | 2020-09-21 18:49:01 +0000 | [diff] [blame] | 61 | TEST_P(FunctionDecoration_StageTest, Emit) { |
| 62 | auto params = GetParam(); |
| 63 | |
dan sinclair | b583993 | 2020-12-16 21:38:40 +0000 | [diff] [blame] | 64 | auto* func = Func("main", {}, ty.void_, ast::StatementList{}, |
| 65 | ast::FunctionDecorationList{ |
Ben Clayton | e6e7041 | 2021-01-05 15:29:29 +0000 | [diff] [blame] | 66 | create<ast::StageDecoration>(params.stage), |
dan sinclair | b583993 | 2020-12-16 21:38:40 +0000 | [diff] [blame] | 67 | }); |
dan sinclair | a8274b2 | 2020-09-21 18:49:01 +0000 | [diff] [blame] | 68 | |
dan sinclair | b583993 | 2020-12-16 21:38:40 +0000 | [diff] [blame] | 69 | ASSERT_TRUE(b.GenerateFunction(func)) << b.error(); |
dan sinclair | a8274b2 | 2020-09-21 18:49:01 +0000 | [diff] [blame] | 70 | |
dan sinclair | 488d7a9 | 2020-10-06 21:20:28 +0000 | [diff] [blame] | 71 | auto preamble = b.entry_points(); |
dan sinclair | d5fd7e0 | 2020-11-03 16:26:09 +0000 | [diff] [blame] | 72 | ASSERT_GE(preamble.size(), 1u); |
dan sinclair | a8274b2 | 2020-09-21 18:49:01 +0000 | [diff] [blame] | 73 | EXPECT_EQ(preamble[0].opcode(), spv::Op::OpEntryPoint); |
| 74 | |
| 75 | ASSERT_GE(preamble[0].operands().size(), 3u); |
| 76 | EXPECT_EQ(preamble[0].operands()[0].to_i(), params.model); |
| 77 | } |
| 78 | INSTANTIATE_TEST_SUITE_P( |
| 79 | BuilderTest, |
| 80 | FunctionDecoration_StageTest, |
| 81 | testing::Values(FunctionStageData{ast::PipelineStage::kVertex, |
| 82 | SpvExecutionModelVertex}, |
| 83 | FunctionStageData{ast::PipelineStage::kFragment, |
| 84 | SpvExecutionModelFragment}, |
| 85 | FunctionStageData{ast::PipelineStage::kCompute, |
| 86 | SpvExecutionModelGLCompute})); |
| 87 | |
| 88 | TEST_F(BuilderTest, FunctionDecoration_Stage_WithUnusedInterfaceIds) { |
Ben Clayton | e6e7041 | 2021-01-05 15:29:29 +0000 | [diff] [blame] | 89 | auto* func = |
| 90 | Func("main", {}, ty.void_, ast::StatementList{}, |
| 91 | ast::FunctionDecorationList{ |
| 92 | create<ast::StageDecoration>(ast::PipelineStage::kVertex), |
| 93 | }); |
Ben Clayton | 234b7de | 2020-12-07 20:45:14 +0000 | [diff] [blame] | 94 | |
dan sinclair | b583993 | 2020-12-16 21:38:40 +0000 | [diff] [blame] | 95 | auto* v_in = Var("my_in", ast::StorageClass::kInput, ty.f32); |
| 96 | auto* v_out = Var("my_out", ast::StorageClass::kOutput, ty.f32); |
| 97 | auto* v_wg = Var("my_wg", ast::StorageClass::kWorkgroup, ty.f32); |
dan sinclair | a8274b2 | 2020-09-21 18:49:01 +0000 | [diff] [blame] | 98 | |
Ben Clayton | b053acf | 2020-11-16 16:31:07 +0000 | [diff] [blame] | 99 | EXPECT_TRUE(b.GenerateGlobalVariable(v_in)) << b.error(); |
| 100 | EXPECT_TRUE(b.GenerateGlobalVariable(v_out)) << b.error(); |
| 101 | EXPECT_TRUE(b.GenerateGlobalVariable(v_wg)) << b.error(); |
dan sinclair | a8274b2 | 2020-09-21 18:49:01 +0000 | [diff] [blame] | 102 | |
Ben Clayton | 2f4096b | 2020-11-18 20:58:20 +0000 | [diff] [blame] | 103 | mod->AddGlobalVariable(v_in); |
| 104 | mod->AddGlobalVariable(v_out); |
| 105 | mod->AddGlobalVariable(v_wg); |
dan sinclair | a8274b2 | 2020-09-21 18:49:01 +0000 | [diff] [blame] | 106 | |
dan sinclair | b583993 | 2020-12-16 21:38:40 +0000 | [diff] [blame] | 107 | ASSERT_TRUE(b.GenerateFunction(func)) << b.error(); |
dan sinclair | 987376c | 2021-01-12 04:34:53 +0000 | [diff] [blame^] | 108 | EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %1 "my_in" |
| 109 | OpName %4 "my_out" |
| 110 | OpName %7 "my_wg" |
| 111 | OpName %11 "main" |
dan sinclair | a8274b2 | 2020-09-21 18:49:01 +0000 | [diff] [blame] | 112 | )"); |
| 113 | EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32 |
| 114 | %2 = OpTypePointer Input %3 |
| 115 | %1 = OpVariable %2 Input |
| 116 | %5 = OpTypePointer Output %3 |
| 117 | %6 = OpConstantNull %3 |
| 118 | %4 = OpVariable %5 Output %6 |
| 119 | %8 = OpTypePointer Workgroup %3 |
| 120 | %7 = OpVariable %8 Workgroup |
| 121 | %10 = OpTypeVoid |
| 122 | %9 = OpTypeFunction %10 |
| 123 | )"); |
dan sinclair | 488d7a9 | 2020-10-06 21:20:28 +0000 | [diff] [blame] | 124 | EXPECT_EQ(DumpInstructions(b.entry_points()), |
dan sinclair | 987376c | 2021-01-12 04:34:53 +0000 | [diff] [blame^] | 125 | R"(OpEntryPoint Vertex %11 "main" |
dan sinclair | a8274b2 | 2020-09-21 18:49:01 +0000 | [diff] [blame] | 126 | )"); |
| 127 | } |
| 128 | |
| 129 | TEST_F(BuilderTest, FunctionDecoration_Stage_WithUsedInterfaceIds) { |
Ben Clayton | e6e7041 | 2021-01-05 15:29:29 +0000 | [diff] [blame] | 130 | auto* func = |
| 131 | Func("main", {}, ty.void_, |
| 132 | ast::StatementList{ |
| 133 | create<ast::AssignmentStatement>(Expr("my_out"), Expr("my_in")), |
| 134 | create<ast::AssignmentStatement>(Expr("my_wg"), Expr("my_wg")), |
| 135 | // Add duplicate usages so we show they don't get |
| 136 | // output multiple times. |
| 137 | create<ast::AssignmentStatement>(Expr("my_out"), Expr("my_in"))}, |
| 138 | ast::FunctionDecorationList{ |
| 139 | create<ast::StageDecoration>(ast::PipelineStage::kVertex), |
| 140 | }); |
dan sinclair | a8274b2 | 2020-09-21 18:49:01 +0000 | [diff] [blame] | 141 | |
dan sinclair | b583993 | 2020-12-16 21:38:40 +0000 | [diff] [blame] | 142 | auto* v_in = Var("my_in", ast::StorageClass::kInput, ty.f32); |
| 143 | auto* v_out = Var("my_out", ast::StorageClass::kOutput, ty.f32); |
| 144 | auto* v_wg = Var("my_wg", ast::StorageClass::kWorkgroup, ty.f32); |
dan sinclair | a8274b2 | 2020-09-21 18:49:01 +0000 | [diff] [blame] | 145 | |
Ben Clayton | b053acf | 2020-11-16 16:31:07 +0000 | [diff] [blame] | 146 | td.RegisterVariableForTesting(v_in); |
| 147 | td.RegisterVariableForTesting(v_out); |
| 148 | td.RegisterVariableForTesting(v_wg); |
dan sinclair | a8274b2 | 2020-09-21 18:49:01 +0000 | [diff] [blame] | 149 | |
dan sinclair | b583993 | 2020-12-16 21:38:40 +0000 | [diff] [blame] | 150 | ASSERT_TRUE(td.DetermineFunction(func)) << td.error(); |
dan sinclair | a8274b2 | 2020-09-21 18:49:01 +0000 | [diff] [blame] | 151 | |
Ben Clayton | b053acf | 2020-11-16 16:31:07 +0000 | [diff] [blame] | 152 | EXPECT_TRUE(b.GenerateGlobalVariable(v_in)) << b.error(); |
| 153 | EXPECT_TRUE(b.GenerateGlobalVariable(v_out)) << b.error(); |
| 154 | EXPECT_TRUE(b.GenerateGlobalVariable(v_wg)) << b.error(); |
dan sinclair | a8274b2 | 2020-09-21 18:49:01 +0000 | [diff] [blame] | 155 | |
Ben Clayton | 2f4096b | 2020-11-18 20:58:20 +0000 | [diff] [blame] | 156 | mod->AddGlobalVariable(v_in); |
| 157 | mod->AddGlobalVariable(v_out); |
| 158 | mod->AddGlobalVariable(v_wg); |
dan sinclair | a8274b2 | 2020-09-21 18:49:01 +0000 | [diff] [blame] | 159 | |
dan sinclair | b583993 | 2020-12-16 21:38:40 +0000 | [diff] [blame] | 160 | ASSERT_TRUE(b.GenerateFunction(func)) << b.error(); |
dan sinclair | 987376c | 2021-01-12 04:34:53 +0000 | [diff] [blame^] | 161 | EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %1 "my_in" |
| 162 | OpName %4 "my_out" |
| 163 | OpName %7 "my_wg" |
| 164 | OpName %11 "main" |
dan sinclair | a8274b2 | 2020-09-21 18:49:01 +0000 | [diff] [blame] | 165 | )"); |
| 166 | EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32 |
| 167 | %2 = OpTypePointer Input %3 |
| 168 | %1 = OpVariable %2 Input |
| 169 | %5 = OpTypePointer Output %3 |
| 170 | %6 = OpConstantNull %3 |
| 171 | %4 = OpVariable %5 Output %6 |
| 172 | %8 = OpTypePointer Workgroup %3 |
| 173 | %7 = OpVariable %8 Workgroup |
| 174 | %10 = OpTypeVoid |
| 175 | %9 = OpTypeFunction %10 |
| 176 | )"); |
dan sinclair | 488d7a9 | 2020-10-06 21:20:28 +0000 | [diff] [blame] | 177 | EXPECT_EQ(DumpInstructions(b.entry_points()), |
dan sinclair | 987376c | 2021-01-12 04:34:53 +0000 | [diff] [blame^] | 178 | R"(OpEntryPoint Vertex %11 "main" %4 %1 |
dan sinclair | a8274b2 | 2020-09-21 18:49:01 +0000 | [diff] [blame] | 179 | )"); |
| 180 | } |
| 181 | |
| 182 | TEST_F(BuilderTest, FunctionDecoration_ExecutionMode_Fragment_OriginUpperLeft) { |
Ben Clayton | e6e7041 | 2021-01-05 15:29:29 +0000 | [diff] [blame] | 183 | auto* func = |
| 184 | Func("main", {}, ty.void_, ast::StatementList{}, |
| 185 | ast::FunctionDecorationList{ |
| 186 | create<ast::StageDecoration>(ast::PipelineStage::kFragment), |
| 187 | }); |
dan sinclair | a8274b2 | 2020-09-21 18:49:01 +0000 | [diff] [blame] | 188 | |
dan sinclair | b583993 | 2020-12-16 21:38:40 +0000 | [diff] [blame] | 189 | ASSERT_TRUE(b.GenerateExecutionModes(func, 3)) << b.error(); |
dan sinclair | 488d7a9 | 2020-10-06 21:20:28 +0000 | [diff] [blame] | 190 | EXPECT_EQ(DumpInstructions(b.execution_modes()), |
dan sinclair | a8274b2 | 2020-09-21 18:49:01 +0000 | [diff] [blame] | 191 | R"(OpExecutionMode %3 OriginUpperLeft |
| 192 | )"); |
| 193 | } |
| 194 | |
Ben Clayton | 8df6284 | 2020-12-15 12:36:08 +0000 | [diff] [blame] | 195 | TEST_F(BuilderTest, FunctionDecoration_ExecutionMode_WorkgroupSize_Default) { |
Ben Clayton | e6e7041 | 2021-01-05 15:29:29 +0000 | [diff] [blame] | 196 | auto* func = |
| 197 | Func("main", {}, ty.void_, ast::StatementList{}, |
| 198 | ast::FunctionDecorationList{ |
| 199 | create<ast::StageDecoration>(ast::PipelineStage::kCompute), |
| 200 | }); |
dan sinclair | a8274b2 | 2020-09-21 18:49:01 +0000 | [diff] [blame] | 201 | |
dan sinclair | b583993 | 2020-12-16 21:38:40 +0000 | [diff] [blame] | 202 | ASSERT_TRUE(b.GenerateExecutionModes(func, 3)) << b.error(); |
dan sinclair | 488d7a9 | 2020-10-06 21:20:28 +0000 | [diff] [blame] | 203 | EXPECT_EQ(DumpInstructions(b.execution_modes()), |
dan sinclair | a8274b2 | 2020-09-21 18:49:01 +0000 | [diff] [blame] | 204 | R"(OpExecutionMode %3 LocalSize 1 1 1 |
| 205 | )"); |
| 206 | } |
| 207 | |
Ben Clayton | 8df6284 | 2020-12-15 12:36:08 +0000 | [diff] [blame] | 208 | TEST_F(BuilderTest, FunctionDecoration_ExecutionMode_WorkgroupSize) { |
Ben Clayton | e6e7041 | 2021-01-05 15:29:29 +0000 | [diff] [blame] | 209 | auto* func = |
| 210 | Func("main", {}, ty.void_, ast::StatementList{}, |
| 211 | ast::FunctionDecorationList{ |
| 212 | create<ast::WorkgroupDecoration>(2u, 4u, 6u), |
| 213 | create<ast::StageDecoration>(ast::PipelineStage::kCompute), |
| 214 | }); |
dan sinclair | a8274b2 | 2020-09-21 18:49:01 +0000 | [diff] [blame] | 215 | |
dan sinclair | b583993 | 2020-12-16 21:38:40 +0000 | [diff] [blame] | 216 | ASSERT_TRUE(b.GenerateExecutionModes(func, 3)) << b.error(); |
dan sinclair | 488d7a9 | 2020-10-06 21:20:28 +0000 | [diff] [blame] | 217 | EXPECT_EQ(DumpInstructions(b.execution_modes()), |
dan sinclair | a8274b2 | 2020-09-21 18:49:01 +0000 | [diff] [blame] | 218 | R"(OpExecutionMode %3 LocalSize 2 4 6 |
| 219 | )"); |
| 220 | } |
| 221 | |
dan sinclair | 488d7a9 | 2020-10-06 21:20:28 +0000 | [diff] [blame] | 222 | TEST_F(BuilderTest, FunctionDecoration_ExecutionMode_MultipleFragment) { |
Ben Clayton | e6e7041 | 2021-01-05 15:29:29 +0000 | [diff] [blame] | 223 | auto* func1 = |
| 224 | Func("main1", {}, ty.void_, ast::StatementList{}, |
| 225 | ast::FunctionDecorationList{ |
| 226 | create<ast::StageDecoration>(ast::PipelineStage::kFragment), |
| 227 | }); |
dan sinclair | 488d7a9 | 2020-10-06 21:20:28 +0000 | [diff] [blame] | 228 | |
Ben Clayton | e6e7041 | 2021-01-05 15:29:29 +0000 | [diff] [blame] | 229 | auto* func2 = |
| 230 | Func("main2", {}, ty.void_, ast::StatementList{}, |
| 231 | ast::FunctionDecorationList{ |
| 232 | create<ast::StageDecoration>(ast::PipelineStage::kFragment), |
| 233 | }); |
dan sinclair | 488d7a9 | 2020-10-06 21:20:28 +0000 | [diff] [blame] | 234 | |
dan sinclair | b583993 | 2020-12-16 21:38:40 +0000 | [diff] [blame] | 235 | ASSERT_TRUE(b.GenerateFunction(func1)) << b.error(); |
| 236 | ASSERT_TRUE(b.GenerateFunction(func2)) << b.error(); |
dan sinclair | 488d7a9 | 2020-10-06 21:20:28 +0000 | [diff] [blame] | 237 | EXPECT_EQ(DumpBuilder(b), |
dan sinclair | 987376c | 2021-01-12 04:34:53 +0000 | [diff] [blame^] | 238 | R"(OpEntryPoint Fragment %3 "main1" |
| 239 | OpEntryPoint Fragment %5 "main2" |
dan sinclair | 488d7a9 | 2020-10-06 21:20:28 +0000 | [diff] [blame] | 240 | OpExecutionMode %3 OriginUpperLeft |
| 241 | OpExecutionMode %5 OriginUpperLeft |
dan sinclair | 987376c | 2021-01-12 04:34:53 +0000 | [diff] [blame^] | 242 | OpName %3 "main1" |
| 243 | OpName %5 "main2" |
dan sinclair | 488d7a9 | 2020-10-06 21:20:28 +0000 | [diff] [blame] | 244 | %2 = OpTypeVoid |
| 245 | %1 = OpTypeFunction %2 |
| 246 | %3 = OpFunction %2 None %1 |
| 247 | %4 = OpLabel |
dan sinclair | a7c9391 | 2020-11-18 18:25:30 +0000 | [diff] [blame] | 248 | OpReturn |
dan sinclair | 488d7a9 | 2020-10-06 21:20:28 +0000 | [diff] [blame] | 249 | OpFunctionEnd |
| 250 | %5 = OpFunction %2 None %1 |
| 251 | %6 = OpLabel |
dan sinclair | a7c9391 | 2020-11-18 18:25:30 +0000 | [diff] [blame] | 252 | OpReturn |
dan sinclair | 488d7a9 | 2020-10-06 21:20:28 +0000 | [diff] [blame] | 253 | OpFunctionEnd |
| 254 | )"); |
| 255 | } |
| 256 | |
Ben Clayton | 8df6284 | 2020-12-15 12:36:08 +0000 | [diff] [blame] | 257 | TEST_F(BuilderTest, FunctionDecoration_ExecutionMode_FragDepth) { |
| 258 | auto* fragdepth = |
dan sinclair | b583993 | 2020-12-16 21:38:40 +0000 | [diff] [blame] | 259 | Var("fragdepth", ast::StorageClass::kOutput, ty.f32, nullptr, |
Ben Clayton | 8df6284 | 2020-12-15 12:36:08 +0000 | [diff] [blame] | 260 | ast::VariableDecorationList{ |
| 261 | create<ast::BuiltinDecoration>(ast::Builtin::kFragDepth), |
| 262 | }); |
| 263 | mod->AddGlobalVariable(fragdepth); |
| 264 | |
dan sinclair | 181d8ba | 2020-12-16 15:15:40 +0000 | [diff] [blame] | 265 | auto* func = |
dan sinclair | b583993 | 2020-12-16 21:38:40 +0000 | [diff] [blame] | 266 | Func("main", ast::VariableList{}, ty.void_, |
dan sinclair | 181d8ba | 2020-12-16 15:15:40 +0000 | [diff] [blame] | 267 | ast::StatementList{ |
| 268 | create<ast::AssignmentStatement>(Expr("fragdepth"), Expr(1.f)), |
| 269 | }, |
| 270 | ast::FunctionDecorationList{}); |
Ben Clayton | 8df6284 | 2020-12-15 12:36:08 +0000 | [diff] [blame] | 271 | |
| 272 | func->add_referenced_module_variable(fragdepth); |
| 273 | |
| 274 | ASSERT_TRUE(b.GenerateExecutionModes(func, 3)) << b.error(); |
| 275 | EXPECT_EQ(DumpInstructions(b.execution_modes()), |
| 276 | R"(OpExecutionMode %3 DepthReplacing |
| 277 | )"); |
| 278 | } |
| 279 | |
dan sinclair | a8274b2 | 2020-09-21 18:49:01 +0000 | [diff] [blame] | 280 | } // namespace |
| 281 | } // namespace spirv |
| 282 | } // namespace writer |
| 283 | } // namespace tint |