From b22b7de24ab24b4ae13a3922b30959ba4f923622 Mon Sep 17 00:00:00 2001 From: Jaddyen Date: Thu, 26 Jun 2025 21:28:05 +0000 Subject: [PATCH 1/7] Started on the buffer_map --- mlir/include/mlir/Dialect/EmitC/IR/EmitC.td | 40 +++++++++++++++++++ .../EmitC/Transforms/WrapFuncInClass.cpp | 23 ++++++++--- 2 files changed, 58 insertions(+), 5 deletions(-) diff --git a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td index 91ee89919e58e..69076261ddd2d 100644 --- a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td +++ b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td @@ -1679,4 +1679,44 @@ def EmitC_GetFieldOp let assemblyFormat = "$field_name `:` type($result) attr-dict"; } +def BufferMapOp + : EmitC_Op<"buffer_map", [Pure, + DeclareOpInterfaceMethods]> { + let summary = "Creates a buffer map for field access"; + let description = [{ + The `emitc.buffer_map` operation generates a C++ std::map that maps field names + to their memory addresses for efficient runtime field access. This operation + collects fields with buffer attributes and creates the necessary lookup + infrastructure. + + Example: + + ```mlir + emitc.buffer_map reflection_map [ @field1, @field2, @field3 ] + ``` + + This generates C++ code like: + + ```cpp + const std::map _reflection_map { + { "field1", reinterpret_cast(&field1) }, + { "field2", reinterpret_cast(&field2) }, + { "field3", reinterpret_cast(&field3) } + }; + + char* getBufferForName(const std::string& name) const { + auto it = _reflection_map.find(name); + return (it == _reflection_map.end()) ? nullptr : it->second; + } + ``` + }]; + + let arguments = (ins SymbolNameAttr:$sym_name, + Arg, "field names">:$fields); + + let results = (outs); + let builders = []; + let assemblyFormat = "$sym_name $fields attr-dict"; +} + #endif // MLIR_DIALECT_EMITC_IR_EMITC diff --git a/mlir/lib/Dialect/EmitC/Transforms/WrapFuncInClass.cpp b/mlir/lib/Dialect/EmitC/Transforms/WrapFuncInClass.cpp index 17d436f6df028..4f2ecfb640d2a 100644 --- a/mlir/lib/Dialect/EmitC/Transforms/WrapFuncInClass.cpp +++ b/mlir/lib/Dialect/EmitC/Transforms/WrapFuncInClass.cpp @@ -53,22 +53,35 @@ class WrapFuncInClass : public OpRewritePattern { ClassOp newClassOp = rewriter.create(funcOp.getLoc(), className); SmallVector> fields; + SmallVector bufferFieldAttrs; rewriter.createBlock(&newClassOp.getBody()); rewriter.setInsertionPointToStart(&newClassOp.getBody().front()); auto argAttrs = funcOp.getArgAttrs(); for (auto [idx, val] : llvm::enumerate(funcOp.getArguments())) { StringAttr fieldName; - Attribute argAttr = nullptr; fieldName = rewriter.getStringAttr("fieldName" + std::to_string(idx)); - if (argAttrs && idx < argAttrs->size()) - argAttr = (*argAttrs)[idx]; - + if (argAttrs && idx < argAttrs->size()) { + mlir::DictionaryAttr dictAttr = + dyn_cast_or_null((*argAttrs)[idx]); + const mlir::Attribute namedAttribute = + dictAttr.getNamed(attributeName)->getValue(); + + auto name = + cast(cast(namedAttribute)[0]); + bufferFieldAttrs.push_back(name); + } TypeAttr typeAttr = TypeAttr::get(val.getType()); fields.push_back({fieldName, typeAttr}); rewriter.create(funcOp.getLoc(), fieldName, typeAttr, - argAttr); + nullptr); + } + + if (!bufferFieldAttrs.empty()) { + ArrayAttr fieldsArrayAttr = rewriter.getArrayAttr(bufferFieldAttrs); + rewriter.create(funcOp.getLoc(), "reflection_map", + fieldsArrayAttr); } rewriter.setInsertionPointToEnd(&newClassOp.getBody().front()); From 310cae6ca05d8aa4c3be1782fb2f51ce56fa4cf3 Mon Sep 17 00:00:00 2001 From: Jaddyen Date: Fri, 27 Jun 2025 18:26:28 +0000 Subject: [PATCH 2/7] Remove sym_name --- mlir/include/mlir/Dialect/EmitC/IR/EmitC.td | 16 +++++++--------- .../Dialect/EmitC/Transforms/WrapFuncInClass.cpp | 3 +-- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td index 69076261ddd2d..919648dc7fb1d 100644 --- a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td +++ b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td @@ -1692,16 +1692,16 @@ def BufferMapOp Example: ```mlir - emitc.buffer_map reflection_map [ @field1, @field2, @field3 ] + emitc.buffer_map [ @field1, @field2, @field3 ] ``` This generates C++ code like: ```cpp - const std::map _reflection_map { - { "field1", reinterpret_cast(&field1) }, - { "field2", reinterpret_cast(&field2) }, - { "field3", reinterpret_cast(&field3) } + const std::map _buffer_map { + { "some_feature", reinterpret_cast(&some_feature) }, + { "another_feature", reinterpret_cast(&another_feature) }, + { "input_tense", reinterpret_cast(&input_tense) } }; char* getBufferForName(const std::string& name) const { @@ -1711,12 +1711,10 @@ def BufferMapOp ``` }]; - let arguments = (ins SymbolNameAttr:$sym_name, - Arg, "field names">:$fields); + let arguments = (ins Arg, "field names">:$fields); let results = (outs); - let builders = []; - let assemblyFormat = "$sym_name $fields attr-dict"; + let assemblyFormat = "$fields attr-dict"; } #endif // MLIR_DIALECT_EMITC_IR_EMITC diff --git a/mlir/lib/Dialect/EmitC/Transforms/WrapFuncInClass.cpp b/mlir/lib/Dialect/EmitC/Transforms/WrapFuncInClass.cpp index 4f2ecfb640d2a..b4fd50fa4fd95 100644 --- a/mlir/lib/Dialect/EmitC/Transforms/WrapFuncInClass.cpp +++ b/mlir/lib/Dialect/EmitC/Transforms/WrapFuncInClass.cpp @@ -80,8 +80,7 @@ class WrapFuncInClass : public OpRewritePattern { if (!bufferFieldAttrs.empty()) { ArrayAttr fieldsArrayAttr = rewriter.getArrayAttr(bufferFieldAttrs); - rewriter.create(funcOp.getLoc(), "reflection_map", - fieldsArrayAttr); + rewriter.create(funcOp.getLoc(), fieldsArrayAttr); } rewriter.setInsertionPointToEnd(&newClassOp.getBody().front()); From 3fca2a40303e84c3ba5e87037ab811c60262d3fb Mon Sep 17 00:00:00 2001 From: Jaddyen Date: Fri, 27 Jun 2025 19:21:27 +0000 Subject: [PATCH 3/7] Corrected the attrs use in fields --- mlir/include/mlir/Dialect/EmitC/IR/EmitC.td | 7 ++----- mlir/lib/Dialect/EmitC/IR/EmitC.cpp | 3 --- mlir/lib/Dialect/EmitC/Transforms/WrapFuncInClass.cpp | 6 ++---- mlir/test/Dialect/EmitC/wrap_emitc_func_in_class.mlir | 7 ++++--- 4 files changed, 8 insertions(+), 15 deletions(-) diff --git a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td index 919648dc7fb1d..f5d163a45b08e 100644 --- a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td +++ b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td @@ -1644,17 +1644,14 @@ def EmitC_FieldOp : EmitC_Op<"field", [Symbol]> { Example: ```mlir - // Example with an attribute: - emitc.field @fieldName0 : !emitc.array<1xf32> {emitc.opaque = "another_feature"} // Example with no attribute: emitc.field @fieldName0 : !emitc.array<1xf32> ``` }]; - let arguments = (ins SymbolNameAttr:$sym_name, TypeAttr:$type, - OptionalAttr:$attrs); + let arguments = (ins SymbolNameAttr:$sym_name, TypeAttr:$type); - let assemblyFormat = [{ $sym_name `:` $type ($attrs^)? attr-dict}]; + let assemblyFormat = [{ $sym_name `:` $type attr-dict}]; let hasVerifier = 1; } diff --git a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp index 27298e892e599..b0ff4ed7bb688 100644 --- a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp +++ b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp @@ -1415,9 +1415,6 @@ LogicalResult FieldOp::verify() { if (!symName || symName.getValue().empty()) return emitOpError("field must have a non-empty symbol name"); - if (!getAttrs()) - return success(); - return success(); } diff --git a/mlir/lib/Dialect/EmitC/Transforms/WrapFuncInClass.cpp b/mlir/lib/Dialect/EmitC/Transforms/WrapFuncInClass.cpp index b4fd50fa4fd95..3e627708c6f3e 100644 --- a/mlir/lib/Dialect/EmitC/Transforms/WrapFuncInClass.cpp +++ b/mlir/lib/Dialect/EmitC/Transforms/WrapFuncInClass.cpp @@ -68,14 +68,12 @@ class WrapFuncInClass : public OpRewritePattern { const mlir::Attribute namedAttribute = dictAttr.getNamed(attributeName)->getValue(); - auto name = - cast(cast(namedAttribute)[0]); + auto name = cast(namedAttribute); bufferFieldAttrs.push_back(name); } TypeAttr typeAttr = TypeAttr::get(val.getType()); fields.push_back({fieldName, typeAttr}); - rewriter.create(funcOp.getLoc(), fieldName, typeAttr, - nullptr); + rewriter.create(funcOp.getLoc(), fieldName, typeAttr); } if (!bufferFieldAttrs.empty()) { diff --git a/mlir/test/Dialect/EmitC/wrap_emitc_func_in_class.mlir b/mlir/test/Dialect/EmitC/wrap_emitc_func_in_class.mlir index c67a0c197fcd9..364840adb3e5f 100644 --- a/mlir/test/Dialect/EmitC/wrap_emitc_func_in_class.mlir +++ b/mlir/test/Dialect/EmitC/wrap_emitc_func_in_class.mlir @@ -19,9 +19,10 @@ module attributes { } { // CHECK: module { // CHECK-NEXT: emitc.class @modelClass { -// CHECK-NEXT: emitc.field @fieldName0 : !emitc.array<1xf32> {emitc.name_hint = "another_feature"} -// CHECK-NEXT: emitc.field @fieldName1 : !emitc.array<1xf32> {emitc.name_hint = "some_feature"} -// CHECK-NEXT: emitc.field @fieldName2 : !emitc.array<1xf32> {emitc.name_hint = "output_0"} +// CHECK-NEXT: emitc.field @fieldName0 : !emitc.array<1xf32> +// CHECK-NEXT: emitc.field @fieldName1 : !emitc.array<1xf32> +// CHECK-NEXT: emitc.field @fieldName2 : !emitc.array<1xf32> +// CHECK-NEXT: emitc.buffer_map ["another_feature", "some_feature", "output_0"] // CHECK-NEXT: emitc.func @execute() { // CHECK-NEXT: get_field @fieldName0 : !emitc.array<1xf32> // CHECK-NEXT: get_field @fieldName1 : !emitc.array<1xf32> From 5b19aacb831ef7c3061654076ec469ad580c8f91 Mon Sep 17 00:00:00 2001 From: Jaddyen Date: Fri, 27 Jun 2025 20:50:03 +0000 Subject: [PATCH 4/7] Example of how we can emit the buffer_map in cpp --- mlir/lib/Target/Cpp/TranslateToCpp.cpp | 46 +++++++++++++++----- mlir/test/mlir-translate/emitc_classops.mlir | 11 +++++ 2 files changed, 46 insertions(+), 11 deletions(-) diff --git a/mlir/lib/Target/Cpp/TranslateToCpp.cpp b/mlir/lib/Target/Cpp/TranslateToCpp.cpp index c04548688bcf6..e9195966930a2 100644 --- a/mlir/lib/Target/Cpp/TranslateToCpp.cpp +++ b/mlir/lib/Target/Cpp/TranslateToCpp.cpp @@ -9,6 +9,7 @@ #include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h" #include "mlir/Dialect/EmitC/IR/EmitC.h" #include "mlir/Dialect/Func/IR/FuncOps.h" +#include "mlir/IR/Attributes.h" #include "mlir/IR/BuiltinOps.h" #include "mlir/IR/BuiltinTypes.h" #include "mlir/IR/Dialect.h" @@ -24,6 +25,7 @@ #include "llvm/ADT/TypeSwitch.h" #include "llvm/Support/Debug.h" #include "llvm/Support/FormatVariadic.h" +#include "llvm/Support/LogicalResult.h" #include #include @@ -1038,6 +1040,28 @@ static LogicalResult printOperation(CppEmitter &emitter, return success(); } +static LogicalResult printOperation(CppEmitter &emitter, + BufferMapOp bufferMapOp) { + raw_indented_ostream &os = emitter.ostream(); + os << "const std::map _buffer_map {\n"; + os.indent(); + auto buf = bufferMapOp.getFields(); + for (auto field : *buf) { + os << "{ \"" << field << "\", reinterpret_cast(&" << field + << ") },\n"; + } + os.unindent(); + os << "};\n"; + + os << "char* getBufferForName(const std::string& name) const {\n"; + os.indent(); + os << "auto it = _buffer_map.find(name);\n"; + os << "return (it == _buffer_map.end()) ? nullptr : it->second;\n"; + os.unindent(); + os << "}\n"; + return success(); +} + static LogicalResult printOperation(CppEmitter &emitter, FileOp file) { if (!emitter.shouldEmitFile(file)) return success(); @@ -1645,17 +1669,17 @@ LogicalResult CppEmitter::emitOperation(Operation &op, bool trailingSemicolon) { .Case( + emitc::BitwiseRightShiftOp, emitc::BitwiseXorOp, + emitc::BufferMapOp, emitc::CallOp, emitc::CallOpaqueOp, + emitc::CastOp, emitc::ClassOp, emitc::CmpOp, + emitc::ConditionalOp, emitc::ConstantOp, emitc::DeclareFuncOp, + emitc::DivOp, emitc::ExpressionOp, emitc::FieldOp, + emitc::FileOp, emitc::ForOp, emitc::FuncOp, emitc::GetFieldOp, + emitc::GlobalOp, emitc::IfOp, emitc::IncludeOp, emitc::LoadOp, + emitc::LogicalAndOp, emitc::LogicalNotOp, emitc::LogicalOrOp, + emitc::MulOp, emitc::RemOp, emitc::ReturnOp, emitc::SubOp, + emitc::SwitchOp, emitc::UnaryMinusOp, emitc::UnaryPlusOp, + emitc::VariableOp, emitc::VerbatimOp>( [&](auto op) { return printOperation(*this, op); }) // Func ops. diff --git a/mlir/test/mlir-translate/emitc_classops.mlir b/mlir/test/mlir-translate/emitc_classops.mlir index e42844412860e..c31538b75895c 100644 --- a/mlir/test/mlir-translate/emitc_classops.mlir +++ b/mlir/test/mlir-translate/emitc_classops.mlir @@ -3,6 +3,7 @@ emitc.class @modelClass { emitc.field @fieldName0 : !emitc.array<1xf32> emitc.field @fieldName1 : !emitc.array<1xf32> + emitc.buffer_map ["another_feature", "some_feature", "output_0"] emitc.func @execute() { %0 = "emitc.constant"() <{value = 0 : index}> : () -> !emitc.size_t %1 = get_field @fieldName0 : !emitc.array<1xf32> @@ -16,6 +17,16 @@ emitc.class @modelClass { // CHECK-NEXT: public: // CHECK-NEXT: float[1] fieldName0; // CHECK-NEXT: float[1] fieldName1; +// CHECK-NEXT: const std::map _buffer_map { +// CHECK-NEXT: { ""another_feature"", reinterpret_cast(&"another_feature") }, +// CHECK-NEXT: { ""some_feature"", reinterpret_cast(&"some_feature") }, +// CHECK-NEXT: { ""output_0"", reinterpret_cast(&"output_0") }, +// CHECK-NEXT: }; +// CHECK-NEXT: char* getBufferForName(const std::string& name) const { +// CHECK-NEXT: auto it = _buffer_map.find(name); +// CHECK-NEXT: return (it == _buffer_map.end()) ? nullptr : it->second; +// CHECK-NEXT: } +// CHECK-EMPTY: // CHECK-NEXT: void execute() { // CHECK-NEXT: size_t v1 = 0; // CHECK-NEXT: float[1] v2 = fieldName0; From f6a1e408fe5b082701c67b354b396e29bdebea55 Mon Sep 17 00:00:00 2001 From: Jaddyen Date: Tue, 1 Jul 2025 16:29:21 +0000 Subject: [PATCH 5/7] cleaning pr --- mlir/lib/Target/Cpp/TranslateToCpp.cpp | 44 +++++--------------- mlir/test/mlir-translate/emitc_classops.mlir | 11 ----- 2 files changed, 11 insertions(+), 44 deletions(-) diff --git a/mlir/lib/Target/Cpp/TranslateToCpp.cpp b/mlir/lib/Target/Cpp/TranslateToCpp.cpp index e9195966930a2..7dcbe6f1607f9 100644 --- a/mlir/lib/Target/Cpp/TranslateToCpp.cpp +++ b/mlir/lib/Target/Cpp/TranslateToCpp.cpp @@ -1040,28 +1040,6 @@ static LogicalResult printOperation(CppEmitter &emitter, return success(); } -static LogicalResult printOperation(CppEmitter &emitter, - BufferMapOp bufferMapOp) { - raw_indented_ostream &os = emitter.ostream(); - os << "const std::map _buffer_map {\n"; - os.indent(); - auto buf = bufferMapOp.getFields(); - for (auto field : *buf) { - os << "{ \"" << field << "\", reinterpret_cast(&" << field - << ") },\n"; - } - os.unindent(); - os << "};\n"; - - os << "char* getBufferForName(const std::string& name) const {\n"; - os.indent(); - os << "auto it = _buffer_map.find(name);\n"; - os << "return (it == _buffer_map.end()) ? nullptr : it->second;\n"; - os.unindent(); - os << "}\n"; - return success(); -} - static LogicalResult printOperation(CppEmitter &emitter, FileOp file) { if (!emitter.shouldEmitFile(file)) return success(); @@ -1669,17 +1647,17 @@ LogicalResult CppEmitter::emitOperation(Operation &op, bool trailingSemicolon) { .Case( + emitc::BitwiseRightShiftOp, emitc::BitwiseXorOp, emitc::CallOp, + emitc::CallOpaqueOp, emitc::CastOp, emitc::ClassOp, + emitc::CmpOp, emitc::ConditionalOp, emitc::ConstantOp, + emitc::DeclareFuncOp, emitc::DivOp, emitc::ExpressionOp, + emitc::FieldOp, emitc::FileOp, emitc::ForOp, emitc::FuncOp, + emitc::GetFieldOp, emitc::GlobalOp, emitc::IfOp, + emitc::IncludeOp, emitc::LoadOp, emitc::LogicalAndOp, + emitc::LogicalNotOp, emitc::LogicalOrOp, emitc::MulOp, + emitc::RemOp, emitc::ReturnOp, emitc::SubOp, emitc::SwitchOp, + emitc::UnaryMinusOp, emitc::UnaryPlusOp, emitc::VariableOp, + emitc::VerbatimOp>( [&](auto op) { return printOperation(*this, op); }) // Func ops. diff --git a/mlir/test/mlir-translate/emitc_classops.mlir b/mlir/test/mlir-translate/emitc_classops.mlir index c31538b75895c..e42844412860e 100644 --- a/mlir/test/mlir-translate/emitc_classops.mlir +++ b/mlir/test/mlir-translate/emitc_classops.mlir @@ -3,7 +3,6 @@ emitc.class @modelClass { emitc.field @fieldName0 : !emitc.array<1xf32> emitc.field @fieldName1 : !emitc.array<1xf32> - emitc.buffer_map ["another_feature", "some_feature", "output_0"] emitc.func @execute() { %0 = "emitc.constant"() <{value = 0 : index}> : () -> !emitc.size_t %1 = get_field @fieldName0 : !emitc.array<1xf32> @@ -17,16 +16,6 @@ emitc.class @modelClass { // CHECK-NEXT: public: // CHECK-NEXT: float[1] fieldName0; // CHECK-NEXT: float[1] fieldName1; -// CHECK-NEXT: const std::map _buffer_map { -// CHECK-NEXT: { ""another_feature"", reinterpret_cast(&"another_feature") }, -// CHECK-NEXT: { ""some_feature"", reinterpret_cast(&"some_feature") }, -// CHECK-NEXT: { ""output_0"", reinterpret_cast(&"output_0") }, -// CHECK-NEXT: }; -// CHECK-NEXT: char* getBufferForName(const std::string& name) const { -// CHECK-NEXT: auto it = _buffer_map.find(name); -// CHECK-NEXT: return (it == _buffer_map.end()) ? nullptr : it->second; -// CHECK-NEXT: } -// CHECK-EMPTY: // CHECK-NEXT: void execute() { // CHECK-NEXT: size_t v1 = 0; // CHECK-NEXT: float[1] v2 = fieldName0; From 3519aaeba7ba905c68ec6ee8d74535a0c1340972 Mon Sep 17 00:00:00 2001 From: Jaden Angella Date: Tue, 1 Jul 2025 14:06:38 -0700 Subject: [PATCH 6/7] Update TranslateToCpp.cpp --- mlir/lib/Target/Cpp/TranslateToCpp.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/mlir/lib/Target/Cpp/TranslateToCpp.cpp b/mlir/lib/Target/Cpp/TranslateToCpp.cpp index 7dcbe6f1607f9..446f710172b95 100644 --- a/mlir/lib/Target/Cpp/TranslateToCpp.cpp +++ b/mlir/lib/Target/Cpp/TranslateToCpp.cpp @@ -9,7 +9,6 @@ #include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h" #include "mlir/Dialect/EmitC/IR/EmitC.h" #include "mlir/Dialect/Func/IR/FuncOps.h" -#include "mlir/IR/Attributes.h" #include "mlir/IR/BuiltinOps.h" #include "mlir/IR/BuiltinTypes.h" #include "mlir/IR/Dialect.h" @@ -26,7 +25,6 @@ #include "llvm/Support/Debug.h" #include "llvm/Support/FormatVariadic.h" #include "llvm/Support/LogicalResult.h" -#include #include #define DEBUG_TYPE "translate-to-cpp" From a5b0dc2f5428d1056e728b010364ca95dffd2f7e Mon Sep 17 00:00:00 2001 From: Jaden Angella Date: Tue, 1 Jul 2025 14:07:19 -0700 Subject: [PATCH 7/7] Update TranslateToCpp.cpp --- mlir/lib/Target/Cpp/TranslateToCpp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlir/lib/Target/Cpp/TranslateToCpp.cpp b/mlir/lib/Target/Cpp/TranslateToCpp.cpp index 446f710172b95..c04548688bcf6 100644 --- a/mlir/lib/Target/Cpp/TranslateToCpp.cpp +++ b/mlir/lib/Target/Cpp/TranslateToCpp.cpp @@ -24,7 +24,7 @@ #include "llvm/ADT/TypeSwitch.h" #include "llvm/Support/Debug.h" #include "llvm/Support/FormatVariadic.h" -#include "llvm/Support/LogicalResult.h" +#include #include #define DEBUG_TYPE "translate-to-cpp"