| // RUN: mlir-translate -mlir-to-llvmir -split-input-file %s | FileCheck %s | |
| // Single scalar reduction on omp.taskloop.context. The lowering must: | |
| // 1. Emit an implicit __kmpc_taskgroup in the encountering function (since | |
| // the user did not write nogroup); | |
| // 2. Build a kmp_taskred_input_t descriptor array and call | |
| // __kmpc_taskred_init, capturing the returned descriptor handle; | |
| // 3. Pass NoGroup=true to OpenMPIRBuilder::createTaskloop so that it does not | |
| // emit a second (implicit) taskgroup around the taskloop; | |
| // 4. Inside the outlined task body, call __kmpc_global_thread_num to obtain | |
| // the executing thread's gtid, then look up the per-task private storage | |
| // via __kmpc_task_reduction_get_th_data(gtid, redDesc, orig); | |
| // 5. Close the implicit taskgroup with __kmpc_end_taskgroup. | |
| omp.declare_reduction @add_i32 : i32 | |
| init { | |
| ^bb0(%arg0: i32): | |
| %c0 = llvm.mlir.constant(0 : i32) : i32 | |
| omp.yield(%c0 : i32) | |
| } | |
| combiner { | |
| ^bb0(%arg0: i32, %arg1: i32): | |
| %s = llvm.add %arg0, %arg1 : i32 | |
| omp.yield(%s : i32) | |
| } | |
| llvm.func @taskloop_reduction_single(%x : !llvm.ptr, %lb : i32, %ub : i32, %step : i32) { | |
| omp.taskloop.context reduction(@add_i32 %x -> %prv : !llvm.ptr) { | |
| omp.taskloop.wrapper { | |
| omp.loop_nest (%iv) : i32 = (%lb) to (%ub) step (%step) { | |
| %v = llvm.load %prv : !llvm.ptr -> i32 | |
| %s = llvm.add %v, %iv : i32 | |
| llvm.store %s, %prv : i32, !llvm.ptr | |
| omp.yield | |
| } | |
| } | |
| omp.terminator | |
| } {omp.combined} | |
| llvm.return | |
| } | |
| // CHECK: %kmp_taskred_input_t = type { ptr, ptr, i64, ptr, ptr, ptr, i32 } | |
| // Encountering function emits taskgroup + descriptor + taskred_init. | |
| // CHECK-LABEL: define void @taskloop_reduction_single( | |
| // CHECK-SAME: ptr %[[X:[^,]+]], | |
| // CHECK: %[[ARR:.+]] = alloca [1 x %kmp_taskred_input_t] | |
| // CHECK: call void @__kmpc_taskgroup( | |
| // CHECK: %[[ELEM:.+]] = getelementptr inbounds [1 x %kmp_taskred_input_t], ptr %[[ARR]], i32 0, i32 0 | |
| // CHECK: %[[SHAR:.+]] = getelementptr {{.+}} %kmp_taskred_input_t, ptr %[[ELEM]], i32 0, i32 0 | |
| // CHECK: store ptr %[[X]], ptr %[[SHAR]] | |
| // CHECK: store ptr @__omp_taskloop_taskred_add_i32.red.init | |
| // CHECK: store ptr @__omp_taskloop_taskred_add_i32.red.comb | |
| // CHECK: %[[DESC:.+]] = call ptr @__kmpc_taskred_init(i32 %{{.+}}, i32 1, ptr %[[ARR]]) | |
| // The returned descriptor is stored into the structArg captured by | |
| // __kmpc_omp_task_alloc so the outlined task body can load it back. | |
| // CHECK: store ptr %[[DESC]], ptr %{{.+}} | |
| // We manually opened a taskgroup above, so the reduction lowering passes | |
| // NoGroup=true to createTaskloop. The __kmpc_taskloop "nogroup" argument is not | |
| // a reliable witness for this (OpenMPIRBuilder hardcodes that operand to 1); | |
| // instead assert that no SECOND implicit taskgroup pair wraps the taskloop. | |
| // CHECK-NOT: call void @__kmpc_taskgroup( | |
| // CHECK-NOT: call void @__kmpc_end_taskgroup( | |
| // CHECK: call void @__kmpc_taskloop( | |
| // CHECK-NOT: call void @__kmpc_taskgroup( | |
| // CHECK: call void @__kmpc_end_taskgroup( | |
| // Outlined task body looks up per-task storage via the runtime, passing the | |
| // reloaded descriptor (not null) as the second argument. | |
| // CHECK-LABEL: define internal void @taskloop_reduction_single..omp_par( | |
| // CHECK: %[[BODY_DESC:.+]] = load ptr, ptr %gep_.taskred.desc | |
| // CHECK: %[[BODY_ORIG:.+]] = load ptr, ptr %gep_, | |
| // CHECK: %[[BODY_GTID:.+]] = call i32 @__kmpc_global_thread_num( | |
| // CHECK: %[[PRIV:.+]] = call ptr @__kmpc_task_reduction_get_th_data(i32 %[[BODY_GTID]], ptr %[[BODY_DESC]], ptr %[[BODY_ORIG]]) | |
| // CHECK: load i32, ptr %[[PRIV]] | |
| // CHECK: store i32 %{{.+}}, ptr %[[PRIV]] | |
| // ----- | |
| // Multiple reductions: each entry in the descriptor array gets distinct | |
| // init / combiner helpers and the body issues one | |
| // __kmpc_task_reduction_get_th_data per reduction. | |
| omp.declare_reduction @add_i32 : i32 | |
| init { | |
| ^bb0(%arg0: i32): | |
| %c0 = llvm.mlir.constant(0 : i32) : i32 | |
| omp.yield(%c0 : i32) | |
| } | |
| combiner { | |
| ^bb0(%arg0: i32, %arg1: i32): | |
| %s = llvm.add %arg0, %arg1 : i32 | |
| omp.yield(%s : i32) | |
| } | |
| omp.declare_reduction @mul_i64 : i64 | |
| init { | |
| ^bb0(%arg0: i64): | |
| %c1 = llvm.mlir.constant(1 : i64) : i64 | |
| omp.yield(%c1 : i64) | |
| } | |
| combiner { | |
| ^bb0(%arg0: i64, %arg1: i64): | |
| %p = llvm.mul %arg0, %arg1 : i64 | |
| omp.yield(%p : i64) | |
| } | |
| llvm.func @taskloop_reduction_multi(%x : !llvm.ptr, %y : !llvm.ptr, %lb : i32, %ub : i32, %step : i32) { | |
| omp.taskloop.context reduction(@add_i32 %x -> %a, @mul_i64 %y -> %b : !llvm.ptr, !llvm.ptr) { | |
| omp.taskloop.wrapper { | |
| omp.loop_nest (%iv) : i32 = (%lb) to (%ub) step (%step) { | |
| %va = llvm.load %a : !llvm.ptr -> i32 | |
| %vai = llvm.add %va, %iv : i32 | |
| llvm.store %vai, %a : i32, !llvm.ptr | |
| %vb = llvm.load %b : !llvm.ptr -> i64 | |
| %iv64 = llvm.sext %iv : i32 to i64 | |
| %vbi = llvm.mul %vb, %iv64 : i64 | |
| llvm.store %vbi, %b : i64, !llvm.ptr | |
| omp.yield | |
| } | |
| } | |
| omp.terminator | |
| } {omp.combined} | |
| llvm.return | |
| } | |
| // CHECK-LABEL: define void @taskloop_reduction_multi( | |
| // CHECK: %[[ARR2:.+]] = alloca [2 x %kmp_taskred_input_t] | |
| // CHECK: call void @__kmpc_taskgroup( | |
| // CHECK: store i64 4 | |
| // CHECK: store ptr @__omp_taskloop_taskred_add_i32.red.init | |
| // CHECK: store ptr @__omp_taskloop_taskred_add_i32.red.comb | |
| // CHECK: store i64 8 | |
| // CHECK: store ptr @__omp_taskloop_taskred_mul_i64.red.init | |
| // CHECK: store ptr @__omp_taskloop_taskred_mul_i64.red.comb | |
| // CHECK: %[[DESC2:.+]] = call ptr @__kmpc_taskred_init(i32 %{{.+}}, i32 2, ptr %[[ARR2]]) | |
| // The descriptor is captured into structArg so the outlined task can reload it. | |
| // CHECK: store ptr %[[DESC2]], ptr %{{.+}} | |
| // As in the single-reduction case, NoGroup is asserted indirectly rather than | |
| // via the hardcoded __kmpc_taskloop "nogroup" argument: no second implicit | |
| // taskgroup pair may wrap the taskloop. | |
| // CHECK-NOT: call void @__kmpc_taskgroup( | |
| // CHECK-NOT: call void @__kmpc_end_taskgroup( | |
| // CHECK: call void @__kmpc_taskloop( | |
| // CHECK-NOT: call void @__kmpc_taskgroup( | |
| // CHECK: call void @__kmpc_end_taskgroup( | |
| // CHECK-LABEL: define internal void @taskloop_reduction_multi..omp_par( | |
| // CHECK: %[[BODY_GTID2:.+]] = call i32 @__kmpc_global_thread_num( | |
| // Both get_th_data calls share the same body gtid; the descriptor argument | |
| // must be a reloaded SSA value (not null). | |
| // CHECK: call ptr @__kmpc_task_reduction_get_th_data(i32 %[[BODY_GTID2]], ptr %{{[^,]+}}, ptr %{{.+}}) | |
| // CHECK: call ptr @__kmpc_task_reduction_get_th_data(i32 %[[BODY_GTID2]], ptr %{{[^,]+}}, ptr %{{.+}}) | |
| // ----- | |
| // in_reduction on omp.taskloop.context nested inside an outer taskgroup | |
| // task_reduction. The taskloop has no reduction clause of its own, so it must | |
| // NOT build a taskred descriptor: the single __kmpc_taskred_init belongs to the | |
| // outer task_reduction. The user did not write nogroup, so the taskloop runs | |
| // with NoGroup=false and createTaskloop opens the taskloop's own implicit | |
| // taskgroup in addition to the outer one. The outlined body's get_th_data call | |
| // passes a NULL descriptor so the runtime walks up to the enclosing taskgroup. | |
| omp.declare_reduction @add_i32 : i32 | |
| init { | |
| ^bb0(%arg0: i32): | |
| %c0 = llvm.mlir.constant(0 : i32) : i32 | |
| omp.yield(%c0 : i32) | |
| } | |
| combiner { | |
| ^bb0(%arg0: i32, %arg1: i32): | |
| %s = llvm.add %arg0, %arg1 : i32 | |
| omp.yield(%s : i32) | |
| } | |
| llvm.func @taskloop_inreduction(%x : !llvm.ptr, %lb : i32, %ub : i32, %step : i32) { | |
| omp.taskgroup task_reduction(@add_i32 %x -> %tg : !llvm.ptr) { | |
| omp.taskloop.context in_reduction(@add_i32 %x -> %prv : !llvm.ptr) { | |
| omp.taskloop.wrapper { | |
| omp.loop_nest (%iv) : i32 = (%lb) to (%ub) step (%step) { | |
| %v = llvm.load %prv : !llvm.ptr -> i32 | |
| %s = llvm.add %v, %iv : i32 | |
| llvm.store %s, %prv : i32, !llvm.ptr | |
| omp.yield | |
| } | |
| } | |
| omp.terminator | |
| } {omp.combined} | |
| omp.terminator | |
| } | |
| llvm.return | |
| } | |
| // CHECK-LABEL: define void @taskloop_inreduction( | |
| // The outer taskgroup task_reduction opens and builds the one descriptor. | |
| // CHECK: call void @__kmpc_taskgroup( | |
| // CHECK: call ptr @__kmpc_taskred_init( | |
| // The taskloop carries only an in_reduction clause, so it must NOT build its | |
| // own reduction descriptor. | |
| // CHECK-NOT: call ptr @__kmpc_taskred_init( | |
| // No nogroup was written, so createTaskloop runs with NoGroup=false and opens | |
| // the taskloop's own implicit taskgroup (separate from the outer one above). | |
| // CHECK: call void @__kmpc_taskgroup( | |
| // CHECK: call void @__kmpc_taskloop( | |
| // CHECK: call void @__kmpc_end_taskgroup( | |
| // In the outlined taskloop task body, the in_reduction lookup passes NULL | |
| // as the descriptor argument so the runtime walks up enclosing taskgroups. | |
| // CHECK-LABEL: define internal void @taskloop_inreduction..omp_par( | |
| // CHECK: call i32 @__kmpc_global_thread_num( | |
| // CHECK: call ptr @__kmpc_task_reduction_get_th_data(i32 %{{.+}}, ptr null, ptr %{{.+}}) | |
| // ----- | |
| // nogroup + in_reduction: the user wrote `nogroup` on the taskloop and only an | |
| // in_reduction clause, so the translator must NOT open an implicit taskgroup | |
| // and must NOT build a taskred descriptor for the taskloop itself, and the | |
| // outlined body must look up the participant with a NULL descriptor so the | |
| // runtime walks up. NoGroup is witnessed below by the absence of any taskgroup | |
| // pair around the taskloop, not by the __kmpc_taskloop "nogroup" operand | |
| // (OpenMPIRBuilder hardcodes that operand to 1 regardless of NoGroup). | |
| omp.declare_reduction @add_i32 : i32 | |
| init { | |
| ^bb0(%arg0: i32): | |
| %c0 = llvm.mlir.constant(0 : i32) : i32 | |
| omp.yield(%c0 : i32) | |
| } | |
| combiner { | |
| ^bb0(%arg0: i32, %arg1: i32): | |
| %s = llvm.add %arg0, %arg1 : i32 | |
| omp.yield(%s : i32) | |
| } | |
| llvm.func @taskloop_nogroup_inreduction(%x : !llvm.ptr, %lb : i32, %ub : i32, %step : i32) { | |
| omp.taskloop.context nogroup in_reduction(@add_i32 %x -> %prv : !llvm.ptr) { | |
| omp.taskloop.wrapper { | |
| omp.loop_nest (%iv) : i32 = (%lb) to (%ub) step (%step) { | |
| %v = llvm.load %prv : !llvm.ptr -> i32 | |
| %s = llvm.add %v, %iv : i32 | |
| llvm.store %s, %prv : i32, !llvm.ptr | |
| omp.yield | |
| } | |
| } | |
| omp.terminator | |
| } {omp.combined} | |
| llvm.return | |
| } | |
| // Outer caller: NoGroup is proven by the absence of any taskgroup pair (and the | |
| // absence of taskred_init) around the taskloop. The __kmpc_taskloop "nogroup" | |
| // argument is not a reliable witness (OpenMPIRBuilder hardcodes that operand to | |
| // 1), so __kmpc_taskloop is matched only as a plain call anchor here. | |
| // CHECK-LABEL: define void @taskloop_nogroup_inreduction( | |
| // CHECK-NOT: call void @__kmpc_taskgroup( | |
| // CHECK-NOT: call ptr @__kmpc_taskred_init( | |
| // CHECK-NOT: call void @__kmpc_end_taskgroup( | |
| // CHECK: call void @__kmpc_taskloop( | |
| // CHECK-NOT: call void @__kmpc_taskgroup( | |
| // CHECK-NOT: call void @__kmpc_end_taskgroup( | |
| // In the outlined task body, the in_reduction lookup uses a NULL descriptor. | |
| // CHECK-LABEL: define internal void @taskloop_nogroup_inreduction..omp_par( | |
| // CHECK: call i32 @__kmpc_global_thread_num( | |
| // CHECK: call ptr @__kmpc_task_reduction_get_th_data(i32 %{{.+}}, ptr null, ptr %{{.+}}) | |
| // ----- | |
| // Non-default address-space taskloop reduction. The reduction item pointer is a | |
| // `!llvm.ptr<1>`, but __kmpc_task_reduction_get_th_data is declared with a | |
| // generic (default-address-space) `ptr` for the original item pointer and | |
| // returns a generic `ptr`. The body-remapping path must therefore: | |
| // 1. addrspacecast the reloaded original pointer to the generic address space | |
| // before the lookup (matching the descriptor normalization done for the | |
| // taskgroup setup), and pass that generic pointer to the runtime call; | |
| // 2. addrspacecast the returned generic private pointer back to addrspace(1) | |
| // before mapping it to the block argument, so the body's loads/stores act | |
| // on the per-task private storage in the expected address space. | |
| omp.declare_reduction @add_i32_as1 : i32 | |
| init { | |
| ^bb0(%arg0: i32): | |
| %c0 = llvm.mlir.constant(0 : i32) : i32 | |
| omp.yield(%c0 : i32) | |
| } | |
| combiner { | |
| ^bb0(%arg0: i32, %arg1: i32): | |
| %s = llvm.add %arg0, %arg1 : i32 | |
| omp.yield(%s : i32) | |
| } | |
| llvm.func @taskloop_reduction_as1(%x : !llvm.ptr<1>, %lb : i32, %ub : i32, %step : i32) { | |
| omp.taskloop.context reduction(@add_i32_as1 %x -> %prv : !llvm.ptr<1>) { | |
| omp.taskloop.wrapper { | |
| omp.loop_nest (%iv) : i32 = (%lb) to (%ub) step (%step) { | |
| %v = llvm.load %prv : !llvm.ptr<1> -> i32 | |
| %s = llvm.add %v, %iv : i32 | |
| llvm.store %s, %prv : i32, !llvm.ptr<1> | |
| omp.yield | |
| } | |
| } | |
| omp.terminator | |
| } {omp.combined} | |
| llvm.return | |
| } | |
| // The encountering function normalizes the addrspace(1) item pointer to generic | |
| // for the taskred descriptor (taskgroup setup), then builds the descriptor. | |
| // CHECK-LABEL: define void @taskloop_reduction_as1( | |
| // CHECK-SAME: ptr addrspace(1) %[[X:[^,]+]], | |
| // CHECK: %[[X_GEN:.+]] = addrspacecast ptr addrspace(1) %[[X]] to ptr | |
| // CHECK: call ptr @__kmpc_taskred_init(i32 %{{.+}}, i32 1, ptr %{{.+}}) | |
| // In the outlined task body the addrspace(1) original is normalized to generic | |
| // before the (non-null descriptor) lookup, and the returned generic private | |
| // pointer is cast back to addrspace(1) for the body's loads/stores. | |
| // CHECK-LABEL: define internal void @taskloop_reduction_as1..omp_par( | |
| // CHECK: %[[BODY_GTID:.+]] = call i32 @__kmpc_global_thread_num( | |
| // CHECK: %[[ORIG_GEN:.+]] = addrspacecast ptr addrspace(1) %{{.+}} to ptr | |
| // CHECK: %[[PRIV:.+]] = call ptr @__kmpc_task_reduction_get_th_data(i32 %[[BODY_GTID]], ptr %{{.+}}, ptr %[[ORIG_GEN]]) | |
| // CHECK: %[[PRIV_AS1:.+]] = addrspacecast ptr %[[PRIV]] to ptr addrspace(1) | |
| // CHECK: load i32, ptr addrspace(1) %[[PRIV_AS1]] | |
| // CHECK: store i32 %{{.+}}, ptr addrspace(1) %[[PRIV_AS1]] | |
| // ----- | |
| // Non-default address-space taskloop in_reduction nested inside an outer | |
| // taskgroup task_reduction. The taskloop carries only an in_reduction clause, so | |
| // its body looks up the participant with a NULL descriptor; the addrspace(1) | |
| // original pointer is still normalized to generic before the lookup and the | |
| // returned generic private pointer is cast back to addrspace(1) for the body. | |
| omp.declare_reduction @add_i32_as1 : i32 | |
| init { | |
| ^bb0(%arg0: i32): | |
| %c0 = llvm.mlir.constant(0 : i32) : i32 | |
| omp.yield(%c0 : i32) | |
| } | |
| combiner { | |
| ^bb0(%arg0: i32, %arg1: i32): | |
| %s = llvm.add %arg0, %arg1 : i32 | |
| omp.yield(%s : i32) | |
| } | |
| llvm.func @taskloop_inreduction_as1(%x : !llvm.ptr<1>, %lb : i32, %ub : i32, %step : i32) { | |
| omp.taskgroup task_reduction(@add_i32_as1 %x -> %tg : !llvm.ptr<1>) { | |
| omp.taskloop.context in_reduction(@add_i32_as1 %x -> %prv : !llvm.ptr<1>) { | |
| omp.taskloop.wrapper { | |
| omp.loop_nest (%iv) : i32 = (%lb) to (%ub) step (%step) { | |
| %v = llvm.load %prv : !llvm.ptr<1> -> i32 | |
| %s = llvm.add %v, %iv : i32 | |
| llvm.store %s, %prv : i32, !llvm.ptr<1> | |
| omp.yield | |
| } | |
| } | |
| omp.terminator | |
| } {omp.combined} | |
| omp.terminator | |
| } | |
| llvm.return | |
| } | |
| // In the outlined task body the addrspace(1) original is normalized to generic | |
| // before the NULL-descriptor in_reduction lookup, and the returned generic | |
| // private pointer is cast back to addrspace(1) for the body's loads/stores. | |
| // CHECK-LABEL: define internal void @taskloop_inreduction_as1..omp_par( | |
| // CHECK: %[[BODY_GTID:.+]] = call i32 @__kmpc_global_thread_num( | |
| // CHECK: %[[ORIG_GEN:.+]] = addrspacecast ptr addrspace(1) %{{.+}} to ptr | |
| // CHECK: %[[PRIV:.+]] = call ptr @__kmpc_task_reduction_get_th_data(i32 %[[BODY_GTID]], ptr null, ptr %[[ORIG_GEN]]) | |
| // CHECK: %[[PRIV_AS1:.+]] = addrspacecast ptr %[[PRIV]] to ptr addrspace(1) | |
| // CHECK: load i32, ptr addrspace(1) %[[PRIV_AS1]] | |
| // CHECK: store i32 %{{.+}}, ptr addrspace(1) %[[PRIV_AS1]] |