diff options
author | Moritz Angermann <moritz.angermann@gmail.com> | 2021-06-17 22:35:14 +0800 |
---|---|---|
committer | Ben Gamari <ben@smart-cactus.org> | 2022-03-02 22:49:17 -0500 |
commit | a20e66f650135c5c1c18168cc723cdd780d338cc (patch) | |
tree | d62b2f868ad29762ad314ff508879baefa8d8a8a | |
parent | 4a34401c017cdf8d1a2bb15aaf7527c24feb5212 (diff) | |
download | haskell-a20e66f650135c5c1c18168cc723cdd780d338cc.tar.gz |
[aarch64 NCG] Add better support for sub-word primops
During the intial NCG development, GHC did not have support for
anything below Words. As such the NCG didn't support any of this
either. AArch64-Darwin however needs support for subword, as
arguments in excess of the first eight (8) passed via registers
are passed on the stack, and there in a packed fashion. Thus
ghc learned about subword sizes. This than lead us to gain
subword primops, and these subsequently highlighted deficiencies
in the AArch64 NCG.
This patch rectifies the ones I found through via the test-suite.
I do not claim this to be exhaustive.
Fixes: #19993
Metric Increase:
T10421
T13035
T13719
T14697
T1969
T9203
T9872a
T9872b
T9872c
T9872d
T9961
haddock.Cabal
haddock.base
parsing001
(cherry picked from commit d79530d17bc9de61a8bda58308c0377d1c3c697b)
-rw-r--r-- | compiler/GHC/CmmToAsm/AArch64/CodeGen.hs | 152 | ||||
-rw-r--r-- | compiler/GHC/CmmToAsm/AArch64/Instr.hs | 25 | ||||
-rw-r--r-- | compiler/GHC/CmmToAsm/AArch64/Ppr.hs | 8 |
3 files changed, 150 insertions, 35 deletions
diff --git a/compiler/GHC/CmmToAsm/AArch64/CodeGen.hs b/compiler/GHC/CmmToAsm/AArch64/CodeGen.hs index d0ed07bcdc..e0fca263b3 100644 --- a/compiler/GHC/CmmToAsm/AArch64/CodeGen.hs +++ b/compiler/GHC/CmmToAsm/AArch64/CodeGen.hs @@ -632,6 +632,15 @@ getRegister' config plat expr where w' = formatToWidth (cmmTypeFormat (cmmRegType plat reg)) r' = getRegisterReg plat reg + CmmMachOp (MO_U_Quot w) [x, y] | w == W8 -> do + (reg_x, _format_x, code_x) <- getSomeReg x + (reg_y, _format_y, code_y) <- getSomeReg y + return $ Any (intFormat w) (\dst -> code_x `appOL` code_y `snocOL` annExpr expr (UXTB (OpReg w reg_x) (OpReg w reg_x)) `snocOL` (UXTB (OpReg w reg_y) (OpReg w reg_y)) `snocOL` (UDIV (OpReg w dst) (OpReg w reg_x) (OpReg w reg_y))) + CmmMachOp (MO_U_Quot w) [x, y] | w == W16 -> do + (reg_x, _format_x, code_x) <- getSomeReg x + (reg_y, _format_y, code_y) <- getSomeReg y + return $ Any (intFormat w) (\dst -> code_x `appOL` code_y `snocOL` annExpr expr (UXTH (OpReg w reg_x) (OpReg w reg_x)) `snocOL` (UXTH (OpReg w reg_y) (OpReg w reg_y)) `snocOL` (UDIV (OpReg w dst) (OpReg w reg_x) (OpReg w reg_y))) + -- 2. Shifts. x << n, x >> n. CmmMachOp (MO_Shl w) [x, (CmmLit (CmmInt n _))] | w == W32, 0 <= n, n < 32 -> do (reg_x, _format_x, code_x) <- getSomeReg x @@ -640,9 +649,51 @@ getRegister' config plat expr (reg_x, _format_x, code_x) <- getSomeReg x return $ Any (intFormat w) (\dst -> code_x `snocOL` annExpr expr (LSL (OpReg w dst) (OpReg w reg_x) (OpImm (ImmInteger n)))) + CmmMachOp (MO_S_Shr w) [x, (CmmLit (CmmInt n _))] | w == W8, 0 <= n, n < 8 -> do + (reg_x, _format_x, code_x) <- getSomeReg x + return $ Any (intFormat w) (\dst -> code_x `snocOL` annExpr expr (SBFX (OpReg w dst) (OpReg w reg_x) (OpImm (ImmInteger n)) (OpImm (ImmInteger (8-n))))) + CmmMachOp (MO_S_Shr w) [x, y] | w == W8 -> do + (reg_x, _format_x, code_x) <- getSomeReg x + (reg_y, _format_y, code_y) <- getSomeReg y + return $ Any (intFormat w) (\dst -> code_x `appOL` code_y `snocOL` annExpr expr (SXTB (OpReg w reg_x) (OpReg w reg_x)) `snocOL` (ASR (OpReg w dst) (OpReg w reg_x) (OpReg w reg_y))) + + CmmMachOp (MO_S_Shr w) [x, (CmmLit (CmmInt n _))] | w == W16, 0 <= n, n < 16 -> do + (reg_x, _format_x, code_x) <- getSomeReg x + return $ Any (intFormat w) (\dst -> code_x `snocOL` annExpr expr (SBFX (OpReg w dst) (OpReg w reg_x) (OpImm (ImmInteger n)) (OpImm (ImmInteger (16-n))))) + CmmMachOp (MO_S_Shr w) [x, y] | w == W16 -> do + (reg_x, _format_x, code_x) <- getSomeReg x + (reg_y, _format_y, code_y) <- getSomeReg y + return $ Any (intFormat w) (\dst -> code_x `appOL` code_y `snocOL` annExpr expr (SXTH (OpReg w reg_x) (OpReg w reg_x)) `snocOL` (ASR (OpReg w dst) (OpReg w reg_x) (OpReg w reg_y))) + + CmmMachOp (MO_S_Shr w) [x, (CmmLit (CmmInt n _))] | w == W32, 0 <= n, n < 32 -> do + (reg_x, _format_x, code_x) <- getSomeReg x + return $ Any (intFormat w) (\dst -> code_x `snocOL` annExpr expr (ASR (OpReg w dst) (OpReg w reg_x) (OpImm (ImmInteger n)))) + + CmmMachOp (MO_S_Shr w) [x, (CmmLit (CmmInt n _))] | w == W64, 0 <= n, n < 64 -> do + (reg_x, _format_x, code_x) <- getSomeReg x + return $ Any (intFormat w) (\dst -> code_x `snocOL` annExpr expr (ASR (OpReg w dst) (OpReg w reg_x) (OpImm (ImmInteger n)))) + + + CmmMachOp (MO_U_Shr w) [x, (CmmLit (CmmInt n _))] | w == W8, 0 <= n, n < 8 -> do + (reg_x, _format_x, code_x) <- getSomeReg x + return $ Any (intFormat w) (\dst -> code_x `snocOL` annExpr expr (UBFX (OpReg w dst) (OpReg w reg_x) (OpImm (ImmInteger n)) (OpImm (ImmInteger (8-n))))) + CmmMachOp (MO_U_Shr w) [x, y] | w == W8 -> do + (reg_x, _format_x, code_x) <- getSomeReg x + (reg_y, _format_y, code_y) <- getSomeReg y + return $ Any (intFormat w) (\dst -> code_x `appOL` code_y `snocOL` annExpr expr (UXTB (OpReg w reg_x) (OpReg w reg_x)) `snocOL` (ASR (OpReg w dst) (OpReg w reg_x) (OpReg w reg_y))) + + CmmMachOp (MO_U_Shr w) [x, (CmmLit (CmmInt n _))] | w == W16, 0 <= n, n < 16 -> do + (reg_x, _format_x, code_x) <- getSomeReg x + return $ Any (intFormat w) (\dst -> code_x `snocOL` annExpr expr (UBFX (OpReg w dst) (OpReg w reg_x) (OpImm (ImmInteger n)) (OpImm (ImmInteger (16-n))))) + CmmMachOp (MO_U_Shr w) [x, y] | w == W16 -> do + (reg_x, _format_x, code_x) <- getSomeReg x + (reg_y, _format_y, code_y) <- getSomeReg y + return $ Any (intFormat w) (\dst -> code_x `appOL` code_y `snocOL` annExpr expr (UXTH (OpReg w reg_x) (OpReg w reg_x)) `snocOL` (ASR (OpReg w dst) (OpReg w reg_x) (OpReg w reg_y))) + CmmMachOp (MO_U_Shr w) [x, (CmmLit (CmmInt n _))] | w == W32, 0 <= n, n < 32 -> do (reg_x, _format_x, code_x) <- getSomeReg x return $ Any (intFormat w) (\dst -> code_x `snocOL` annExpr expr (LSR (OpReg w dst) (OpReg w reg_x) (OpImm (ImmInteger n)))) + CmmMachOp (MO_U_Shr w) [x, (CmmLit (CmmInt n _))] | w == W64, 0 <= n, n < 64 -> do (reg_x, _format_x, code_x) <- getSomeReg x return $ Any (intFormat w) (\dst -> code_x `snocOL` annExpr expr (LSR (OpReg w dst) (OpReg w reg_x) (OpImm (ImmInteger n)))) @@ -695,6 +746,13 @@ getRegister' config plat expr -- and thus we end up with <float> + <float> => MO_Add <float> <float> MO_Add w -> genOp w (\d x y -> unitOL $ annExpr expr (ADD d x y)) MO_Sub w -> genOp w (\d x y -> unitOL $ annExpr expr (SUB d x y)) + + -- Note [CSET] + -- + -- Setting conditional flags: the architecture internally knows the + -- following flag bits. And based on thsoe comparisons as in the + -- table below. + -- -- 31 30 29 28 -- .---+---+---+---+-- - - -- | N | Z | C | V | @@ -726,10 +784,13 @@ getRegister' config plat expr -- | AL | Always | Any | 1110 | -- | NV | Never | Any | 1111 | --- '-------------------------------------------------------------------------' - - MO_Eq w -> intOp w (\d x y -> toOL [ CMP x y, CSET d EQ ]) - MO_Ne w -> intOp w (\d x y -> toOL [ CMP x y, CSET d NE ]) - MO_Mul w -> intOp w (\d x y -> unitOL $ MUL d x y) + MO_Eq w@W8 -> intOp w (\d x y -> toOL [ SXTB x x, SXTB y y, CMP x y, CSET d EQ ]) + MO_Eq w@W16 -> intOp w (\d x y -> toOL [ SXTH x x, SXTH y y, CMP x y, CSET d EQ ]) + MO_Eq w -> intOp w (\d x y -> toOL [ CMP x y, CSET d EQ ]) + MO_Ne w@W8 -> intOp w (\d x y -> toOL [ SXTB x x, SXTB y y, CMP x y, CSET d NE ]) + MO_Ne w@W16 -> intOp w (\d x y -> toOL [ SXTH x x, SXTH y y, CMP x y, CSET d NE ]) + MO_Ne w -> intOp w (\d x y -> toOL [ CMP x y, CSET d NE ]) + MO_Mul w -> intOp w (\d x y -> unitOL $ MUL d x y) -- Signed multiply/divide MO_S_MulMayOflo w -> intOp w (\d x y -> toOL [ MUL d x y, CSET d VS ]) @@ -751,17 +812,33 @@ getRegister' config plat expr MO_U_Rem w -> withTempIntReg w $ \t -> intOp w (\d x y -> toOL [ UDIV t x y, MSUB d t y x ]) - -- Signed comparisons -- see above for the CSET discussion - MO_S_Ge w -> intOp w (\d x y -> toOL [ CMP x y, CSET d SGE ]) - MO_S_Le w -> intOp w (\d x y -> toOL [ CMP x y, CSET d SLE ]) - MO_S_Gt w -> intOp w (\d x y -> toOL [ CMP x y, CSET d SGT ]) - MO_S_Lt w -> intOp w (\d x y -> toOL [ CMP x y, CSET d SLT ]) + -- Signed comparisons -- see Note [CSET] + MO_S_Ge w@W8 -> intOp w (\d x y -> toOL [ SXTB x x, SXTB y y, CMP x y, CSET d SGE ]) + MO_S_Ge w@W16 -> intOp w (\d x y -> toOL [ SXTH x x, SXTH y y, CMP x y, CSET d SGE ]) + MO_S_Ge w -> intOp w (\d x y -> toOL [ CMP x y, CSET d SGE ]) + MO_S_Le w@W8 -> intOp w (\d x y -> toOL [ SXTB x x, SXTB y y, CMP x y, CSET d SLE ]) + MO_S_Le w@W16 -> intOp w (\d x y -> toOL [ SXTH x x, SXTH y y, CMP x y, CSET d SLE ]) + MO_S_Le w -> intOp w (\d x y -> toOL [ CMP x y, CSET d SLE ]) + MO_S_Gt w@W8 -> intOp w (\d x y -> toOL [ SXTB x x, SXTB y y, CMP x y, CSET d SGT ]) + MO_S_Gt w@W16 -> intOp w (\d x y -> toOL [ SXTH x x, SXTH y y, CMP x y, CSET d SGT ]) + MO_S_Gt w -> intOp w (\d x y -> toOL [ CMP x y, CSET d SGT ]) + MO_S_Lt w@W8 -> intOp w (\d x y -> toOL [ SXTB x x, SXTB y y, CMP x y, CSET d SLT ]) + MO_S_Lt w@W16 -> intOp w (\d x y -> toOL [ SXTH x x, SXTH y y, CMP x y, CSET d SLT ]) + MO_S_Lt w -> intOp w (\d x y -> toOL [ CMP x y, CSET d SLT ]) -- Unsigned comparisons - MO_U_Ge w -> intOp w (\d x y -> toOL [ CMP x y, CSET d UGE ]) - MO_U_Le w -> intOp w (\d x y -> toOL [ CMP x y, CSET d ULE ]) - MO_U_Gt w -> intOp w (\d x y -> toOL [ CMP x y, CSET d UGT ]) - MO_U_Lt w -> intOp w (\d x y -> toOL [ CMP x y, CSET d ULT ]) + MO_U_Ge w@W8 -> intOp w (\d x y -> toOL [ UXTB x x, UXTB y y, CMP x y, CSET d UGE ]) + MO_U_Ge w@W16 -> intOp w (\d x y -> toOL [ UXTH x x, UXTH y y, CMP x y, CSET d UGE ]) + MO_U_Ge w -> intOp w (\d x y -> toOL [ CMP x y, CSET d UGE ]) + MO_U_Le w@W8 -> intOp w (\d x y -> toOL [ UXTB x x, UXTB y y, CMP x y, CSET d ULE ]) + MO_U_Le w@W16 -> intOp w (\d x y -> toOL [ UXTH x x, UXTH y y, CMP x y, CSET d ULE ]) + MO_U_Le w -> intOp w (\d x y -> toOL [ CMP x y, CSET d ULE ]) + MO_U_Gt w@W8 -> intOp w (\d x y -> toOL [ UXTB x x, UXTB y y, CMP x y, CSET d UGT ]) + MO_U_Gt w@W16 -> intOp w (\d x y -> toOL [ UXTH x x, UXTH y y, CMP x y, CSET d UGT ]) + MO_U_Gt w -> intOp w (\d x y -> toOL [ CMP x y, CSET d UGT ]) + MO_U_Lt w@W8 -> intOp w (\d x y -> toOL [ UXTB x x, UXTB y y, CMP x y, CSET d ULT ]) + MO_U_Lt w@W16 -> intOp w (\d x y -> toOL [ UXTH x x, UXTH y y, CMP x y, CSET d ULT ]) + MO_U_Lt w -> intOp w (\d x y -> toOL [ CMP x y, CSET d ULT ]) -- Floating point arithmetic MO_F_Add w -> floatOp w (\d x y -> unitOL $ ADD d x y) @@ -947,11 +1024,28 @@ genCondJump bid expr = do -- Generic case. CmmMachOp mop [x, y] -> do - let bcond w cmp = do - -- compute both sides. - (reg_x, _format_x, code_x) <- getSomeReg x - (reg_y, _format_y, code_y) <- getSomeReg y - return $ code_x `appOL` code_y `snocOL` CMP (OpReg w reg_x) (OpReg w reg_y) `snocOL` (annExpr expr (BCOND cmp (TBlock bid))) + let ubcond w cmp = do + -- compute both sides. + (reg_x, _format_x, code_x) <- getSomeReg x + (reg_y, _format_y, code_y) <- getSomeReg y + let x' = OpReg w reg_x + y' = OpReg w reg_y + return $ case w of + W8 -> code_x `appOL` code_y `appOL` toOL [ UXTB x' x', UXTB y' y', CMP x' y', (annExpr expr (BCOND cmp (TBlock bid))) ] + W16 -> code_x `appOL` code_y `appOL` toOL [ UXTH x' x', UXTH y' y', CMP x' y', (annExpr expr (BCOND cmp (TBlock bid))) ] + _ -> code_x `appOL` code_y `appOL` toOL [ CMP x' y', (annExpr expr (BCOND cmp (TBlock bid))) ] + + sbcond w cmp = do + -- compute both sides. + (reg_x, _format_x, code_x) <- getSomeReg x + (reg_y, _format_y, code_y) <- getSomeReg y + let x' = OpReg w reg_x + y' = OpReg w reg_y + return $ case w of + W8 -> code_x `appOL` code_y `appOL` toOL [ SXTB x' x', SXTB y' y', CMP x' y', (annExpr expr (BCOND cmp (TBlock bid))) ] + W16 -> code_x `appOL` code_y `appOL` toOL [ SXTH x' x', SXTH y' y', CMP x' y', (annExpr expr (BCOND cmp (TBlock bid))) ] + _ -> code_x `appOL` code_y `appOL` toOL [ CMP x' y', (annExpr expr (BCOND cmp (TBlock bid))) ] + fbcond w cmp = do -- ensure we get float regs (reg_fx, _format_fx, code_fx) <- getFloatReg x @@ -967,17 +1061,17 @@ genCondJump bid expr = do MO_F_Lt w -> fbcond w OLT MO_F_Le w -> fbcond w OLE - MO_Eq w -> bcond w EQ - MO_Ne w -> bcond w NE - - MO_S_Gt w -> bcond w SGT - MO_S_Ge w -> bcond w SGE - MO_S_Lt w -> bcond w SLT - MO_S_Le w -> bcond w SLE - MO_U_Gt w -> bcond w UGT - MO_U_Ge w -> bcond w UGE - MO_U_Lt w -> bcond w ULT - MO_U_Le w -> bcond w ULE + MO_Eq w -> sbcond w EQ + MO_Ne w -> sbcond w NE + + MO_S_Gt w -> sbcond w SGT + MO_S_Ge w -> sbcond w SGE + MO_S_Lt w -> sbcond w SLT + MO_S_Le w -> sbcond w SLE + MO_U_Gt w -> ubcond w UGT + MO_U_Ge w -> ubcond w UGE + MO_U_Lt w -> ubcond w ULT + MO_U_Le w -> ubcond w ULE _ -> pprPanic "AArch64.genCondJump:case mop: " (text $ show expr) _ -> pprPanic "AArch64.genCondJump: " (text $ show expr) diff --git a/compiler/GHC/CmmToAsm/AArch64/Instr.hs b/compiler/GHC/CmmToAsm/AArch64/Instr.hs index 7b2be3e4c8..189f57464b 100644 --- a/compiler/GHC/CmmToAsm/AArch64/Instr.hs +++ b/compiler/GHC/CmmToAsm/AArch64/Instr.hs @@ -87,7 +87,12 @@ regUsageOfInstr platform instr = case instr of -- 2. Bit Manipulation Instructions ------------------------------------------ SBFM dst src _ _ -> usage (regOp src, regOp dst) UBFM dst src _ _ -> usage (regOp src, regOp dst) - + SBFX dst src _ _ -> usage (regOp src, regOp dst) + UBFX dst src _ _ -> usage (regOp src, regOp dst) + SXTB dst src -> usage (regOp src, regOp dst) + UXTB dst src -> usage (regOp src, regOp dst) + SXTH dst src -> usage (regOp src, regOp dst) + UXTH dst src -> usage (regOp src, regOp dst) -- 3. Logical and Move Instructions ------------------------------------------ AND dst src1 src2 -> usage (regOp src1 ++ regOp src2, regOp dst) ASR dst src1 src2 -> usage (regOp src1 ++ regOp src2, regOp dst) @@ -211,6 +216,12 @@ patchRegsOfInstr instr env = case instr of -- 2. Bit Manipulation Instructions ---------------------------------------- SBFM o1 o2 o3 o4 -> SBFM (patchOp o1) (patchOp o2) (patchOp o3) (patchOp o4) UBFM o1 o2 o3 o4 -> UBFM (patchOp o1) (patchOp o2) (patchOp o3) (patchOp o4) + SBFX o1 o2 o3 o4 -> SBFX (patchOp o1) (patchOp o2) (patchOp o3) (patchOp o4) + UBFX o1 o2 o3 o4 -> UBFX (patchOp o1) (patchOp o2) (patchOp o3) (patchOp o4) + SXTB o1 o2 -> SXTB (patchOp o1) (patchOp o2) + UXTB o1 o2 -> UXTB (patchOp o1) (patchOp o2) + SXTH o1 o2 -> SXTH (patchOp o1) (patchOp o2) + UXTH o1 o2 -> UXTH (patchOp o1) (patchOp o2) -- 3. Logical and Move Instructions ---------------------------------------- AND o1 o2 o3 -> AND (patchOp o1) (patchOp o2) (patchOp o3) @@ -520,11 +531,10 @@ data Instr | DELTA Int -- 0. Pseudo Instructions -------------------------------------------------- - -- These are instructions not contained or only partially contained in the - -- official ISA, but make reading clearer. Some of them might even be - -- implemented in the assembler, but are not guaranteed to be portable. - -- | SXTB Operand Operand - -- | SXTH Operand Operand + | SXTB Operand Operand + | UXTB Operand Operand + | SXTH Operand Operand + | UXTH Operand Operand -- | SXTW Operand Operand -- | SXTX Operand Operand | PUSH_STACK_FRAME @@ -571,6 +581,9 @@ data Instr | UBFM Operand Operand Operand Operand -- rd = rn[i,j] -- UXTB = UBFM <Wd>, <Wn>, #0, #7 -- UXTH = UBFM <Wd>, <Wn>, #0, #15 + -- Signed/Unsigned bitfield extract + | SBFX Operand Operand Operand Operand -- rd = rn[i,j] + | UBFX Operand Operand Operand Operand -- rd = rn[i,j] -- 3. Logical and Move Instructions ---------------------------------------- | AND Operand Operand Operand -- rd = rn & op2 diff --git a/compiler/GHC/CmmToAsm/AArch64/Ppr.hs b/compiler/GHC/CmmToAsm/AArch64/Ppr.hs index 44e73b29a2..9d1dea085a 100644 --- a/compiler/GHC/CmmToAsm/AArch64/Ppr.hs +++ b/compiler/GHC/CmmToAsm/AArch64/Ppr.hs @@ -423,6 +423,14 @@ pprInstr platform instr = case instr of -- 2. Bit Manipulation Instructions ------------------------------------------ SBFM o1 o2 o3 o4 -> text "\tsbfm" <+> pprOp platform o1 <> comma <+> pprOp platform o2 <> comma <+> pprOp platform o3 <> comma <+> pprOp platform o4 UBFM o1 o2 o3 o4 -> text "\tubfm" <+> pprOp platform o1 <> comma <+> pprOp platform o2 <> comma <+> pprOp platform o3 <> comma <+> pprOp platform o4 + -- signed and unsigned bitfield extract + SBFX o1 o2 o3 o4 -> text "\tsbfx" <+> pprOp platform o1 <> comma <+> pprOp platform o2 <> comma <+> pprOp platform o3 <> comma <+> pprOp platform o4 + UBFX o1 o2 o3 o4 -> text "\tubfx" <+> pprOp platform o1 <> comma <+> pprOp platform o2 <> comma <+> pprOp platform o3 <> comma <+> pprOp platform o4 + SXTB o1 o2 -> text "\tsxtb" <+> pprOp platform o1 <> comma <+> pprOp platform o2 + UXTB o1 o2 -> text "\tuxtb" <+> pprOp platform o1 <> comma <+> pprOp platform o2 + SXTH o1 o2 -> text "\tsxth" <+> pprOp platform o1 <> comma <+> pprOp platform o2 + UXTH o1 o2 -> text "\tuxth" <+> pprOp platform o1 <> comma <+> pprOp platform o2 + -- 3. Logical and Move Instructions ------------------------------------------ AND o1 o2 o3 -> text "\tand" <+> pprOp platform o1 <> comma <+> pprOp platform o2 <> comma <+> pprOp platform o3 ANDS o1 o2 o3 -> text "\tands" <+> pprOp platform o1 <> comma <+> pprOp platform o2 <> comma <+> pprOp platform o3 |