From d1c49901dfe068323ffd8a3f5a98dc0505b3632a Mon Sep 17 00:00:00 2001 From: Danylo Piliaiev Date: Mon, 15 Nov 2021 16:57:38 +0200 Subject: [PATCH] ir3: Add gen4 new subgroup instructions * getlast.w8 #4 - Perform jump for the first (CLUSTER_SIZE-1) fibers in a subgroup * brcst.active.w8 - necessary to implement arithmetic subgroup operations with prefix sum. * quad_shuffle.brcst - subgroupQuadBroadcast * quad_shuffle.horiz - subgroupQuadSwapHorizontal * quad_shuffle.vert - subgroupQuadSwapVertical * quad_shuffle.diag - subgroupQuadSwapDiagonal * getfiberid - gl_SubgroupID Signed-off-by: Danylo Piliaiev Part-of: --- src/freedreno/ir3/disasm-a3xx.c | 7 + src/freedreno/ir3/instr-a3xx.h | 11 +- src/freedreno/ir3/ir3.h | 2 + src/freedreno/ir3/ir3_lexer.l | 19 +- src/freedreno/ir3/ir3_parser.y | 26 ++- src/freedreno/ir3/tests/disasm.c | 11 ++ src/freedreno/isa/encode.c | 1 + src/freedreno/isa/ir3-cat0.xml | 32 ++++ src/freedreno/isa/ir3-cat5.xml | 298 +++++++++++++++++++++---------- src/freedreno/isa/ir3-cat6.xml | 23 +++ 10 files changed, 319 insertions(+), 111 deletions(-) diff --git a/src/freedreno/ir3/disasm-a3xx.c b/src/freedreno/ir3/disasm-a3xx.c index 24b3805085c..362db68b763 100644 --- a/src/freedreno/ir3/disasm-a3xx.c +++ b/src/freedreno/ir3/disasm-a3xx.c @@ -173,6 +173,7 @@ static const struct opc_info { OPC(0, OPC_STKR, stkr), OPC(0, OPC_XSET, xset), OPC(0, OPC_XCLR, xclr), + OPC(0, OPC_GETLAST, getlast), OPC(0, OPC_GETONE, getone), OPC(0, OPC_DBG, dbg), OPC(0, OPC_SHPS, shps), @@ -300,6 +301,11 @@ static const struct opc_info { OPC(5, OPC_DSYPP_1, dsypp.1), OPC(5, OPC_RGETPOS, rgetpos), OPC(5, OPC_RGETINFO, rgetinfo), + OPC(5, OPC_BRCST_ACTIVE, brcst.active), + OPC(5, OPC_QUAD_SHUFFLE_BRCST, quad_shuffle.brcst), + OPC(5, OPC_QUAD_SHUFFLE_HORIZ, quad_shuffle.horiz), + OPC(5, OPC_QUAD_SHUFFLE_VERT, quad_shuffle.vert), + OPC(5, OPC_QUAD_SHUFFLE_DIAG, quad_shuffle.diag), /* macros are needed here for ir3_print */ OPC(5, OPC_DSXPP_MACRO, dsxpp.macro), OPC(5, OPC_DSYPP_MACRO, dsypp.macro), @@ -377,6 +383,7 @@ static const struct opc_info { OPC(6, OPC_ENDLS, endls), OPC(6, OPC_GETSPID, getspid), OPC(6, OPC_GETWID, getwid), + OPC(6, OPC_GETFIBERID, getfiberid), OPC(6, OPC_SPILL_MACRO, spill.macro), OPC(6, OPC_RELOAD_MACRO, reload.macro), diff --git a/src/freedreno/ir3/instr-a3xx.h b/src/freedreno/ir3/instr-a3xx.h index 8a85f575ddb..cffe78b1442 100644 --- a/src/freedreno/ir3/instr-a3xx.h +++ b/src/freedreno/ir3/instr-a3xx.h @@ -80,6 +80,7 @@ typedef enum { OPC_DBG = _OPC(0, 22), OPC_SHPS = _OPC(0, 23), /* shader prologue start */ OPC_SHPE = _OPC(0, 24), /* shader prologue end */ + OPC_GETLAST = _OPC(0, 25), OPC_PREDT = _OPC(0, 29), /* predicated true */ OPC_PREDF = _OPC(0, 30), /* predicated false */ @@ -245,9 +246,14 @@ typedef enum { OPC_DSYPP_1 = _OPC(5, 25), OPC_RGETPOS = _OPC(5, 26), OPC_RGETINFO = _OPC(5, 27), + OPC_BRCST_ACTIVE = _OPC(5, 28), + OPC_QUAD_SHUFFLE_BRCST = _OPC(5, 29), + OPC_QUAD_SHUFFLE_HORIZ = _OPC(5, 30), + OPC_QUAD_SHUFFLE_VERT = _OPC(5, 31), + OPC_QUAD_SHUFFLE_DIAG = _OPC(5, 32), /* cat5 meta instructions, placed above the cat5 opc field's size */ - OPC_DSXPP_MACRO = _OPC(5, 32), - OPC_DSYPP_MACRO = _OPC(5, 33), + OPC_DSXPP_MACRO = _OPC(5, 35), + OPC_DSYPP_MACRO = _OPC(5, 36), /* category 6: */ OPC_LDG = _OPC(6, 0), /* load-global */ @@ -286,6 +292,7 @@ typedef enum { OPC_ENDLS = _OPC(6, 35), /* ??? */ OPC_GETSPID = _OPC(6, 36), /* SP ID */ OPC_GETWID = _OPC(6, 37), /* wavefront ID */ + OPC_GETFIBERID = _OPC(6, 38), /* fiber ID */ /* Logical opcodes for things that differ in a6xx+ */ OPC_STC = _OPC(6, 40), diff --git a/src/freedreno/ir3/ir3.h b/src/freedreno/ir3/ir3.h index 20b2298b7b1..4e081977295 100644 --- a/src/freedreno/ir3/ir3.h +++ b/src/freedreno/ir3/ir3.h @@ -330,6 +330,7 @@ struct ir3_instruction { struct { unsigned samp, tex; unsigned tex_base : 3; + unsigned cluster_size : 4; type_t type; } cat5; struct { @@ -2171,6 +2172,7 @@ ir3_SAM(struct ir3_block *block, opc_t opc, type_t type, unsigned wrmask, } /* cat6 instructions: */ +INSTR0(GETFIBERID) INSTR2(LDLV) INSTR3(LDG) INSTR3(LDL) diff --git a/src/freedreno/ir3/ir3_lexer.l b/src/freedreno/ir3/ir3_lexer.l index 2353a049eb9..1a496e2a791 100644 --- a/src/freedreno/ir3/ir3_lexer.l +++ b/src/freedreno/ir3/ir3_lexer.l @@ -72,16 +72,6 @@ static int parse_reg(const char *str) return num; } -static int parse_w(const char *str) -{ - str++; - unsigned num = strtol(str, NULL, 10); - if ((num % 32) != 0) - yy_fatal_error("w# must be multiple of 32"); - if (num < 32) - yy_fatal_error("w# must be at least 32"); - return num / 32; -} %} %option noyywrap @@ -139,7 +129,7 @@ static int parse_w(const char *str) "a0.x" return T_A0; "a1.x" return T_A1; "p0."[xyzw] ir3_yylval.num = parse_reg(yytext); return T_P0; -"w"[0-9]+ ir3_yylval.num = parse_w(yytext); return T_W; +"w"[0-9]+ ir3_yylval.num = strtol(yytext+1, NULL, 10); return T_W; "s#"[0-9]+ ir3_yylval.num = strtol(yytext+2, NULL, 10); return T_SAMP; "t#"[0-9]+ ir3_yylval.num = strtol(yytext+2, NULL, 10); return T_TEX; @@ -167,6 +157,7 @@ static int parse_w(const char *str) "stkr" return TOKEN(T_OP_STKR); "xset" return TOKEN(T_OP_XSET); "xclr" return TOKEN(T_OP_XCLR); +"getlast" return TOKEN(T_OP_GETLAST); "getone" return TOKEN(T_OP_GETONE); "dbg" return TOKEN(T_OP_DBG); "shps" return TOKEN(T_OP_SHPS); @@ -296,6 +287,11 @@ static int parse_w(const char *str) "dsypp.1" return TOKEN(T_OP_DSYPP_1); "rgetpos" return TOKEN(T_OP_RGETPOS); "rgetinfo" return TOKEN(T_OP_RGETINFO); +"brcst.active" return TOKEN(T_OP_BRCST_A); +"quad_shuffle.brcst" return TOKEN(T_OP_QSHUFFLE_BRCST); +"quad_shuffle.horiz" return TOKEN(T_OP_QSHUFFLE_H); +"quad_shuffle.vert" return TOKEN(T_OP_QSHUFFLE_V); +"quad_shuffle.diag" return TOKEN(T_OP_QSHUFFLE_DIAG); /* category 6: */ "ldg" return TOKEN(T_OP_LDG); @@ -369,6 +365,7 @@ static int parse_w(const char *str) "ldlv" return TOKEN(T_OP_LDLV); "getspid" return TOKEN(T_OP_GETSPID); "getwid" return TOKEN(T_OP_GETWID); +"getfiberid" return TOKEN(T_OP_GETFIBERID); /* category 7: */ "bar" return TOKEN(T_OP_BAR); diff --git a/src/freedreno/ir3/ir3_parser.y b/src/freedreno/ir3/ir3_parser.y index 674e2fe2003..adb9c9e334b 100644 --- a/src/freedreno/ir3/ir3_parser.y +++ b/src/freedreno/ir3/ir3_parser.y @@ -399,6 +399,7 @@ static void print_token(FILE *file, int type, YYSTYPE value) %token T_OP_STKR %token T_OP_XSET %token T_OP_XCLR +%token T_OP_GETLAST %token T_OP_GETONE %token T_OP_DBG %token T_OP_SHPS @@ -526,6 +527,11 @@ static void print_token(FILE *file, int type, YYSTYPE value) %token T_OP_DSYPP_1 %token T_OP_RGETPOS %token T_OP_RGETINFO +%token T_OP_BRCST_A +%token T_OP_QSHUFFLE_BRCST +%token T_OP_QSHUFFLE_H +%token T_OP_QSHUFFLE_V +%token T_OP_QSHUFFLE_DIAG /* category 6: */ %token T_OP_LDG @@ -598,6 +604,7 @@ static void print_token(FILE *file, int type, YYSTYPE value) %token T_OP_LDLV %token T_OP_GETSPID %token T_OP_GETWID +%token T_OP_GETFIBERID /* category 7: */ %token T_OP_BAR @@ -822,6 +829,7 @@ cat0_instr: T_OP_NOP { new_instr(OPC_NOP); } | T_OP_PREDT { new_instr(OPC_PREDT); } cat0_src1 | T_OP_PREDF { new_instr(OPC_PREDF); } cat0_src1 | T_OP_PREDE { new_instr(OPC_PREDE); } +| T_OP_GETLAST '.' T_W { new_instr(OPC_GETLAST); } cat0_immed cat1_opc: T_OP_MOV '.' T_CAT1_TYPE_TYPE { parse_type_type(new_instr(OPC_MOV), $3); @@ -837,9 +845,16 @@ cat1_movmsk: T_OP_MOVMSK '.' T_W { new_instr(OPC_MOVMSK); instr->cat1.src_type = TYPE_U32; instr->cat1.dst_type = TYPE_U32; - instr->repeat = $3 - 1; } dst_reg { - instr->dsts[0]->wrmask = (1 << $3) - 1; + if (($3 % 32) != 0) + yyerror("w# must be multiple of 32"); + if ($3 < 32) + yyerror("w# must be at least 32"); + + int num = $3 / 32; + + instr->repeat = num - 1; + instr->dsts[0]->wrmask = (1 << num) - 1; } cat1_mova1: T_OP_MOVA1 T_A1 ',' { @@ -995,6 +1010,11 @@ cat5_opc: T_OP_ISAM { new_instr(OPC_ISAM); } | T_OP_SAMGP3 { new_instr(OPC_SAMGP3); } | T_OP_RGETPOS { new_instr(OPC_RGETPOS); } | T_OP_RGETINFO { new_instr(OPC_RGETINFO); } +| T_OP_BRCST_A { new_instr(OPC_BRCST_ACTIVE); } +| T_OP_QSHUFFLE_BRCST { new_instr(OPC_QUAD_SHUFFLE_BRCST); } +| T_OP_QSHUFFLE_H { new_instr(OPC_QUAD_SHUFFLE_HORIZ); } +| T_OP_QSHUFFLE_V { new_instr(OPC_QUAD_SHUFFLE_VERT); } +| T_OP_QSHUFFLE_DIAG { new_instr(OPC_QUAD_SHUFFLE_DIAG); } cat5_flag: '.' T_3D { instr->flags |= IR3_INSTR_3D; } | '.' 'a' { instr->flags |= IR3_INSTR_A; } @@ -1005,6 +1025,7 @@ cat5_flag: '.' T_3D { instr->flags |= IR3_INSTR_3D; } | '.' T_UNIFORM { } | '.' T_NONUNIFORM { instr->flags |= IR3_INSTR_NONUNIF; } | '.' T_BASE { instr->flags |= IR3_INSTR_B; instr->cat5.tex_base = $2; } +| '.' T_W { instr->cat5.cluster_size = $2; } cat5_flags: | cat5_flag cat5_flags @@ -1136,6 +1157,7 @@ cat6_ibo: cat6_ibo_opc_1src cat6_type cat6_dim dst_reg ',' 'g' '[' cat6 cat6_id_opc: T_OP_GETSPID { new_instr(OPC_GETSPID); } | T_OP_GETWID { new_instr(OPC_GETWID); } +| T_OP_GETFIBERID { new_instr(OPC_GETFIBERID); } cat6_id: cat6_id_opc cat6_type dst_reg diff --git a/src/freedreno/ir3/tests/disasm.c b/src/freedreno/ir3/tests/disasm.c index 679c843bb3f..325cd70d4fa 100644 --- a/src/freedreno/ir3/tests/disasm.c +++ b/src/freedreno/ir3/tests/disasm.c @@ -69,6 +69,7 @@ static const struct test { INSTR_6XX(00900000_00000003, "br !p0.x, #3"), INSTR_6XX(03820000_00000015, "shps #21"), /* emit */ INSTR_6XX(04021000_00000000, "(ss)shpe"), /* cut */ + INSTR_6XX(02220000_00000004, "getlast.w8 #4"), INSTR_6XX(02820000_00000014, "getone #20"), /* kill p0.x */ INSTR_6XX(00906020_00000007, "brao !p0.x, !p0.y, #7"), INSTR_6XX(00804040_00000003, "braa p0.x, p0.y, #3"), @@ -157,6 +158,13 @@ static const struct test { INSTR_6XX(a048d107_cc080a07, "isaml.base3 (s32)(x)r1.w, r0.w, r1.y, s#0, t#6"), + /* dEQP-VK.subgroups.arithmetic.compute.subgroupadd_float */ + INSTR_6XX(a7c03102_00100003, "brcst.active.w8 (u32)(x)r0.z, r0.y"), /* brcst.active.w8 (u32)(xOOO)r0.z, r0.y */ + /* dEQP-VK.subgroups.quad.graphics.subgroupquadbroadcast_int */ + INSTR_6XX(b7e03107_00000401, "(sy)quad_shuffle.brcst (u32)(x)r1.w, r0.x, r0.z"), /* (sy)quad_shuffle.brcst (u32)(xOOO)r1.w, r0.x, r0.z */ + /* dEQP-VK.subgroups.quad.graphics.subgroupquadswapdiagonal_int */ + INSTR_6XX(b7e03104_00180001, "(sy)quad_shuffle.diag (u32)(x)r1.x, r0.x"), /* (sy)quad_shuffle.diag (u32)(xOOO)r1.x, r0.x */ + /* cat6 */ INSTR_5XX(c6e60000_00010600, "ldgb.untyped.4d.u32.1 r0.x, g[0], r1.x, r0.x"), /* ldgb.a.untyped.1dtype.u32.1 r0.x, g[r1.x], r0.x, 0 */ @@ -374,6 +382,9 @@ static const struct test { /* dEQP-VK.descriptor_indexing.sampler */ INSTR_6XX(a0c81f00_40000005, "sam.s2en.nonuniform.base0 (f32)(xyzw)r0.x, r0.z, r0.x"), + /* dEQP-VK.subgroups.quad.graphics.subgroupquadbroadcast_int */ + INSTR_6XX(c0260001_00c98000, "getfiberid.u32 r0.y"), + /* Custom test since we've never seen the blob emit these. */ INSTR_6XX(c0260004_00490000, "getspid.u32 r1.x"), INSTR_6XX(c0260005_00494000, "getwid.u32 r1.y"), diff --git a/src/freedreno/isa/encode.c b/src/freedreno/isa/encode.c index 1c638cc171d..4e61d685583 100644 --- a/src/freedreno/isa/encode.c +++ b/src/freedreno/isa/encode.c @@ -22,6 +22,7 @@ */ #include "util/log.h" +#include "util/u_math.h" #include "ir3/ir3.h" #include "ir3/ir3_shader.h" diff --git a/src/freedreno/isa/ir3-cat0.xml b/src/freedreno/isa/ir3-cat0.xml index bb6074011f4..5c0c0caa205 100644 --- a/src/freedreno/isa/ir3-cat0.xml +++ b/src/freedreno/isa/ir3-cat0.xml @@ -171,6 +171,38 @@ SOFTWARE. 0000 + + + Perform a jump for all fibers in the first cluster with any active + fibers, except for the last fiber in the cluster. + While there is a separate field for CLUSTER_SIZE its value does + not change the behaviour in any observable way, it behaves as if + CLUSTER_SIZE is always 8. + + + + + + {SY}{SS}{JP}{NAME}.w{CLUSTER_SIZE} #{IMMED} + + + + 2 << {W} + + + xxxxx + xxx + xxx + xx1 + 0100 + + + + + util_logbase2(8) - 1 + + + xx1 0101 diff --git a/src/freedreno/isa/ir3-cat5.xml b/src/freedreno/isa/ir3-cat5.xml index dc2ee6ac44e..73e1a985db2 100644 --- a/src/freedreno/isa/ir3-cat5.xml +++ b/src/freedreno/isa/ir3-cat5.xml @@ -52,6 +52,80 @@ SOFTWARE. + + The "normal" case, ie. not s2en (indirect) and/or bindless + + + {SY}{JP}{NAME}{3D}{A}{O}{P}{S} {TYPE}({WRMASK}){DST_HALF}{DST}{SRC1}{SRC2}{SAMP}{TEX} + + + + + + + + + + + + + + + 0x + + + + + + + + + + + + + + 0 + + + + + + + + + 101 + + extract_cat5_FULL(src) + src + src + src->dsts[0]->wrmask + src + src + src->cat5.tex_base >> 1 + !!(src->flags & IR3_INSTR_3D) + !!(src->flags & IR3_INSTR_A) + !!(src->flags & IR3_INSTR_S) + !!(src->flags & (IR3_INSTR_S2EN | IR3_INSTR_B)) + !!(src->flags & IR3_INSTR_O) + extract_cat5_DESC_MODE(src) + + extract_cat5_SRC(src, 0) + extract_cat5_SRC(src, 1) + (src->srcs_count > 0) ? src->srcs[0] : NULL + + + + {S2EN_BINDLESS} @@ -79,82 +153,15 @@ SOFTWARE. - - The "normal" case, ie. not s2en (indirect) and/or bindless - - - {SY}{JP}{NAME}{3D}{A}{O}{P}{S} {TYPE}({WRMASK}){DST_HALF}{DST}{SRC1}{SRC2}{SAMP}{TEX} - - - - - - - - - - - - - - - 0x 00 - - - - - - - - - - - - - 0 - - - - - - - - - 101 + - extract_cat5_FULL(src) - src - src - src->dsts[0]->wrmask - src - src - src->cat5.tex_base >> 1 - !!(src->flags & IR3_INSTR_3D) - !!(src->flags & IR3_INSTR_A) - !!(src->flags & IR3_INSTR_S) - !!(src->flags & (IR3_INSTR_S2EN | IR3_INSTR_B)) - !!(src->flags & IR3_INSTR_O) !!(src->flags & IR3_INSTR_P) - extract_cat5_DESC_MODE(src) - - extract_cat5_SRC(src, 0) - extract_cat5_SRC(src, 1) - (src->srcs_count > 0) ? src->srcs[0] : NULL - + 00000 @@ -162,7 +169,7 @@ SOFTWARE. - + 00001 @@ -170,7 +177,7 @@ SOFTWARE. - + 00010 @@ -178,7 +185,7 @@ SOFTWARE. - + 00011 @@ -186,7 +193,7 @@ SOFTWARE. - + 00100 @@ -194,7 +201,7 @@ SOFTWARE. - + 00101 @@ -202,7 +209,7 @@ SOFTWARE. - + 00110 @@ -210,7 +217,7 @@ SOFTWARE. - + 00111 @@ -218,7 +225,7 @@ SOFTWARE. - + 01000 @@ -226,7 +233,7 @@ SOFTWARE. - + 01001 @@ -234,7 +241,7 @@ SOFTWARE. - + 01010 @@ -242,7 +249,7 @@ SOFTWARE. - + 01011 @@ -250,7 +257,7 @@ SOFTWARE. - + 01100 @@ -258,7 +265,7 @@ SOFTWARE. - + 01101 @@ -266,7 +273,7 @@ SOFTWARE. - + 01110 @@ -274,7 +281,7 @@ SOFTWARE. - + 01111 @@ -282,7 +289,7 @@ SOFTWARE. - + 10000 @@ -290,7 +297,7 @@ SOFTWARE. - + 10001 @@ -298,7 +305,7 @@ SOFTWARE. - + 10010 @@ -306,7 +313,7 @@ SOFTWARE. - + 10011 @@ -314,7 +321,7 @@ SOFTWARE. - + 10100 @@ -322,7 +329,7 @@ SOFTWARE. - + 10101 @@ -330,7 +337,7 @@ SOFTWARE. - + 10110 @@ -338,7 +345,7 @@ SOFTWARE. - + 10111 @@ -346,7 +353,7 @@ SOFTWARE. - + 11000 @@ -354,7 +361,7 @@ SOFTWARE. - + 11001 @@ -362,7 +369,7 @@ SOFTWARE. - + 11010 @@ -370,7 +377,7 @@ SOFTWARE. - + 11011 @@ -378,6 +385,105 @@ SOFTWARE. + + + The subgroup is divided into (subgroup_size / CLUSTER_SIZE) + clusters. For each cluster brcst.active.w does: + + Given a cluster of fibers f_0, f_1, ..., f_{CLUSTER_SIZE-1} brcst + broadcasts the SRC value from the fiber f_{CLUSTER_SIZE/2-1} + to fibers f_{CLUSTER_SIZE/2}, ..., f_{CLUSTER_SIZE-1}. The DST reg + in other fibers is unaffected. If fiber f_{CLUSTER_SIZE/2-1} is + inactive the value to broadcast is taken from lower fibers + f_{CLUSTER_SIZE/2-2}, f_{CLUSTER_SIZE/2-3}, ... + If all fibers f_0, f_1, ..., f_{CLUSTER_SIZE/2-1} are inactive + the DST reg remains unchanged for all fibers. + + It is necessary in order to implement arithmetic subgroup + operations with prefix sum (https://en.wikipedia.org/wiki/Prefix_sum). + + For brcst.active.w8 without inactive fibers: + Fiber | 0 1 2 3 4 5 6 7 | 8 9 10 11 12 13 14 15 + SRC | s0 s1 s2 s3 ... s7 | s8 ... s11 ... s15 + DST_before | d0 d1 ... d7 | d8 ... d15 + DST_after | d0 d1 d2 d3 s3 s3 s3 s3 | d8 ... d11 s11 s11 s11 s11 + + If fibers 2 and 3 are inactive: + Fiber | 0 1 X X 4 5 6 7 | ... + SRC | s0 s1 X X ... s7 | ... + DST_before | d0 d1 ... d7 | ... + DST_after | d0 d1 X X s1 s1 s1 s1 | ... + + + + + + {SY}{JP}{NAME}.w{CLUSTER_SIZE} {TYPE}({WRMASK}){DST_HALF}{DST}{SRC1} + + + + 111110 + + + + 2 << {W} + + + + + + + + + util_logbase2(src->cat5.cluster_size) - 1 + + + + + + + + {SY}{JP}{NAME} {TYPE}({WRMASK}){DST_HALF}{DST}{SRC1}{SRC2} + + + 111111 + + + + + + + + subgroupQuadBroadcast + + 00 + + + + + + subgroupQuadSwapHorizontal + + 01 + + + + + + subgroupQuadSwapVertical + + 10 + + + + + + subgroupQuadSwapDiagonal + + 11 + + + + x + 100110 + 11xx + xxxxxxxx + + xxxxxxxx + + 1x + + RESourceINFO - returns image/ssbo dimensions (3 components)