From 1752786900095fecb07aa28fa6eeb75e0e3f8d1a Mon Sep 17 00:00:00 2001 From: =?utf8?q?Christian=20W=C3=BCrdig?= Date: Fri, 7 Apr 2006 09:06:03 +0000 Subject: [PATCH] added addtional statistics changed name prefix for SSE floating point nodes from f to x --- ir/be/ia32/bearch_ia32.c | 6 +++-- ir/be/ia32/ia32_dbg_stat.h | 24 ++++++++++++++++++ ir/be/ia32/ia32_emitter.c | 2 +- ir/be/ia32/ia32_map_regs.c | 4 +-- ir/be/ia32/ia32_new_nodes.c | 12 ++++----- ir/be/ia32/ia32_optimize.c | 4 +-- ir/be/ia32/ia32_spec.pl | 26 ++++++++++---------- ir/be/ia32/ia32_transform.c | 49 ++++++++++++++++++++----------------- 8 files changed, 79 insertions(+), 48 deletions(-) diff --git a/ir/be/ia32/bearch_ia32.c b/ir/be/ia32/bearch_ia32.c index ce5a9c0d3..b21206d33 100644 --- a/ir/be/ia32/bearch_ia32.c +++ b/ir/be/ia32/bearch_ia32.c @@ -230,6 +230,8 @@ static arch_irn_class_t ia32_classify(const void *self, const ir_node *irn) { irn = my_skip_proj(irn); if (is_cfop(irn)) return arch_irn_class_branch; + else if (is_ia32_Cnst(irn)) + return arch_irn_class_const; else if (is_ia32_irn(irn)) return arch_irn_class_normal; else @@ -650,7 +652,7 @@ static void transform_to_Load(ia32_transform_env_t *env) { if (mode_is_float(mode)) { if (USE_SSE2(env->cg)) - new_op = new_rd_ia32_fLoad(env->dbg, env->irg, env->block, ptr, noreg, mem, mode_T); + new_op = new_rd_ia32_xLoad(env->dbg, env->irg, env->block, ptr, noreg, mem, mode_T); else new_op = new_rd_ia32_vfld(env->dbg, env->irg, env->block, ptr, noreg, mem, mode_T); } @@ -705,7 +707,7 @@ static void transform_to_Store(ia32_transform_env_t *env) { if (mode_is_float(mode)) { if (USE_SSE2(env->cg)) - new_op = new_rd_ia32_fStore(env->dbg, env->irg, env->block, ptr, noreg, val, nomem, mode_T); + new_op = new_rd_ia32_xStore(env->dbg, env->irg, env->block, ptr, noreg, val, nomem, mode_T); else new_op = new_rd_ia32_vfst(env->dbg, env->irg, env->block, ptr, noreg, val, nomem, mode_T); } diff --git a/ir/be/ia32/ia32_dbg_stat.h b/ir/be/ia32/ia32_dbg_stat.h index 262683a15..889a2ee26 100644 --- a/ir/be/ia32/ia32_dbg_stat.h +++ b/ir/be/ia32/ia32_dbg_stat.h @@ -186,4 +186,28 @@ __dbg_info_merge_pair(load, rload, dbg_backend); \ } while(0) +/** + * A Sub was transformed into Neg-Add due to 2 address code limitations + * + * @param sub the old Sub + * @param nadd the new Add + */ +#define DBG_OPT_SUB2NEGADD(sub, nadd) \ + do { \ + hook_merge_nodes(&nadd, 1, &sub, 1, FS_BE_IA32_SUB2NEGADD); \ + __dbg_info_merge_pair(nadd, sub, dbg_backend); \ + } while(0) + +/** + * A Lea was transformed back into an Add + * + * @param lea the old Lea + * @param nadd the new Add + */ +#define DBG_OPT_LEA2ADD(lea, nadd) \ + do { \ + hook_merge_nodes(&nadd, 1, &lea, 1, FS_BE_IA32_LEA2ADD); \ + __dbg_info_merge_pair(nadd, lea, dbg_backend); \ + } while(0) + #endif /* _IA32_DBG_STAT_H_ */ diff --git a/ir/be/ia32/ia32_emitter.c b/ir/be/ia32/ia32_emitter.c index ec363f397..d00649206 100644 --- a/ir/be/ia32/ia32_emitter.c +++ b/ir/be/ia32/ia32_emitter.c @@ -352,7 +352,7 @@ char *ia32_emit_binop(const ir_node *n, ia32_emit_env_t *env) { (!(is_ia32_St(n) || \ is_ia32_Store8Bit(n) || \ is_ia32_CondJmp(n) || \ - is_ia32_fCondJmp(n) || \ + is_ia32_xCondJmp(n) || \ is_ia32_SwitchJmp(n))) if (! buf) { diff --git a/ir/be/ia32/ia32_map_regs.c b/ir/be/ia32/ia32_map_regs.c index c6efb8d4d..b8bc38e58 100644 --- a/ir/be/ia32/ia32_map_regs.c +++ b/ir/be/ia32/ia32_map_regs.c @@ -257,11 +257,11 @@ long ia32_translate_proj_pos(const ir_node *proj) { return 1; assert(0 && "unsupported DivMod"); } - else if (is_ia32_fDiv(pred)) { + else if (is_ia32_xDiv(pred)) { if (nr == pn_Quot_res) return 0; else - assert(0 && "there should be no more Projs for a fDiv"); + assert(0 && "there should be no more Projs for a xDiv"); } else if (get_irn_mode(proj) == mode_X && nr == pn_Start_X_initial_exec) { return 0; diff --git a/ir/be/ia32/ia32_new_nodes.c b/ir/be/ia32/ia32_new_nodes.c index 294c5d369..437f0c835 100644 --- a/ir/be/ia32/ia32_new_nodes.c +++ b/ir/be/ia32/ia32_new_nodes.c @@ -1119,24 +1119,24 @@ int is_ia32_AddrModeD(const ir_node *node) { } /** - * Checks if node is a Load or fLoad/vfLoad. + * Checks if node is a Load or xLoad/vfLoad. */ int is_ia32_Ld(const ir_node *node) { - return is_ia32_Load(node) || is_ia32_fLoad(node) || is_ia32_vfld(node) || is_ia32_fld(node); + return is_ia32_Load(node) || is_ia32_xLoad(node) || is_ia32_vfld(node) || is_ia32_fld(node); } /** - * Checks if node is a Store or fStore/vfStore. + * Checks if node is a Store or xStore/vfStore. */ int is_ia32_St(const ir_node *node) { - return is_ia32_Store(node) || is_ia32_fStore(node) || is_ia32_vfst(node) || is_ia32_fst(node) || is_ia32_fstp(node); + return is_ia32_Store(node) || is_ia32_xStore(node) || is_ia32_vfst(node) || is_ia32_fst(node) || is_ia32_fstp(node); } /** - * Checks if node is a Const or fConst/vfConst. + * Checks if node is a Const or xConst/vfConst. */ int is_ia32_Cnst(const ir_node *node) { - return is_ia32_Const(node) || is_ia32_fConst(node) || is_ia32_vfConst(node); + return is_ia32_Const(node) || is_ia32_xConst(node) || is_ia32_vfConst(node); } /** diff --git a/ir/be/ia32/ia32_optimize.c b/ir/be/ia32/ia32_optimize.c index ea107be42..0b71ff773 100644 --- a/ir/be/ia32/ia32_optimize.c +++ b/ir/be/ia32/ia32_optimize.c @@ -86,7 +86,7 @@ static ir_node *gen_SymConst(ia32_transform_env_t *env) { if (mode_is_float(mode)) { FP_USED(env->cg); if (USE_SSE2(env->cg)) - cnst = new_rd_ia32_fConst(dbg, irg, block, mode); + cnst = new_rd_ia32_xConst(dbg, irg, block, mode); else cnst = new_rd_ia32_vfConst(dbg, irg, block, mode); } @@ -1328,7 +1328,7 @@ void ia32_optimize_am(ir_node *irn, void *env) { if (ia32_get_irn_n_edges(succ) == 1) { succ = get_edge_src_irn(get_irn_out_edge_first(succ)); - if (is_ia32_fStore(succ) || is_ia32_Store(succ)) { + if (is_ia32_xStore(succ) || is_ia32_Store(succ)) { store = succ; addr_b = get_irn_n(store, 0); addr_i = get_irn_n(store, 1); diff --git a/ir/be/ia32/ia32_spec.pl b/ir/be/ia32/ia32_spec.pl index e892c0619..27ad5cd5a 100644 --- a/ir/be/ia32/ia32_spec.pl +++ b/ir/be/ia32/ia32_spec.pl @@ -503,7 +503,7 @@ $comment_string = "/*"; # commutative operations -"fAdd" => { +"xAdd" => { "irn_flags" => "R", "comment" => "construct SSE Add: Add(a, b) = Add(b, a) = a + b", "cmp_attr" => " return ia32_compare_immop_attr(attr_a, attr_b);\n", @@ -511,7 +511,7 @@ $comment_string = "/*"; "emit" => '. adds%M %ia32_emit_binop /* SSE Add(%A3, %A4) -> %D1 */' }, -"fMul" => { +"xMul" => { "irn_flags" => "R", "comment" => "construct SSE Mul: Mul(a, b) = Mul(b, a) = a * b", "cmp_attr" => " return ia32_compare_immop_attr(attr_a, attr_b);\n", @@ -519,7 +519,7 @@ $comment_string = "/*"; "emit" => '. muls%M %ia32_emit_binop /* SSE Mul(%A3, %A4) -> %D1 */' }, -"fMax" => { +"xMax" => { "irn_flags" => "R", "comment" => "construct SSE Max: Max(a, b) = Max(b, a) = a > b ? a : b", "cmp_attr" => " return ia32_compare_immop_attr(attr_a, attr_b);\n", @@ -527,7 +527,7 @@ $comment_string = "/*"; "emit" => '. maxs%M %ia32_emit_binop /* SSE Max(%A3, %A4) -> %D1 */' }, -"fMin" => { +"xMin" => { "irn_flags" => "R", "comment" => "construct SSE Min: Min(a, b) = Min(b, a) = a < b ? a : b", "cmp_attr" => " return ia32_compare_immop_attr(attr_a, attr_b);\n", @@ -535,7 +535,7 @@ $comment_string = "/*"; "emit" => '. mins%M %ia32_emit_binop /* SSE Min(%A3, %A4) -> %D1 */' }, -"fAnd" => { +"xAnd" => { "irn_flags" => "R", "comment" => "construct SSE And: And(a, b) = a AND b", "cmp_attr" => " return ia32_compare_immop_attr(attr_a, attr_b);\n", @@ -543,7 +543,7 @@ $comment_string = "/*"; "emit" => '. andp%M %ia32_emit_binop /* SSE And(%A3, %A4) -> %D1 */' }, -"fOr" => { +"xOr" => { "irn_flags" => "R", "comment" => "construct SSE Or: Or(a, b) = a OR b", "cmp_attr" => " return ia32_compare_immop_attr(attr_a, attr_b);\n", @@ -551,7 +551,7 @@ $comment_string = "/*"; "emit" => '. orp%M %ia32_emit_binop /* SSE Or(%A3, %A4) -> %D1 */' }, -"fEor" => { +"xEor" => { "irn_flags" => "R", "comment" => "construct SSE Eor: Eor(a, b) = a XOR b", "cmp_attr" => " return ia32_compare_immop_attr(attr_a, attr_b);\n", @@ -561,7 +561,7 @@ $comment_string = "/*"; # not commutative operations -"fSub" => { +"xSub" => { "irn_flags" => "R", "comment" => "construct SSE Sub: Sub(a, b) = a - b", "cmp_attr" => " return ia32_compare_immop_attr(attr_a, attr_b);\n", @@ -569,7 +569,7 @@ $comment_string = "/*"; "emit" => '. subs%M %ia32_emit_binop /* SSE Sub(%A1, %A2) -> %D1 */' }, -"fDiv" => { +"xDiv" => { "irn_flags" => "R", "comment" => "construct SSE Div: Div(a, b) = a / b", "cmp_attr" => " return ia32_compare_immop_attr(attr_a, attr_b);\n", @@ -579,14 +579,14 @@ $comment_string = "/*"; # other operations -"fCondJmp" => { +"xCondJmp" => { "op_flags" => "L|X|Y", "comment" => "construct conditional jump: UCOMIS A, B && JMPxx LABEL", "cmp_attr" => " return ia32_compare_immop_attr(attr_a, attr_b);\n", "reg_req" => { "in" => [ "gp", "gp", "xmm", "xmm", "none" ], "out" => [ "none", "none" ] }, }, -"fConst" => { +"xConst" => { "op_flags" => "c", "irn_flags" => "R", "comment" => "represents a SSE constant", @@ -597,7 +597,7 @@ $comment_string = "/*"; # Load / Store -"fLoad" => { +"xLoad" => { "op_flags" => "L|F", "irn_flags" => "R", "state" => "exc_pinned", @@ -607,7 +607,7 @@ $comment_string = "/*"; "emit" => '. movs%M %D1, %ia32_emit_am /* Load((%A1)) -> %D1 */' }, -"fStore" => { +"xStore" => { "op_flags" => "L|F", "state" => "exc_pinned", "comment" => "construct Store: Store(ptr, val, mem) = ST ptr,val", diff --git a/ir/be/ia32/ia32_transform.c b/ir/be/ia32/ia32_transform.c index 5108be190..ebbd38114 100644 --- a/ir/be/ia32/ia32_transform.c +++ b/ir/be/ia32/ia32_transform.c @@ -36,6 +36,7 @@ #include "ia32_transform.h" #include "ia32_new_nodes.h" #include "ia32_map_regs.h" +#include "ia32_dbg_stat.h" #include "gen_ia32_regalloc_if.h" @@ -449,7 +450,7 @@ static ir_node *gen_Add(ia32_transform_env_t *env) { if (mode_is_float(mode)) { FP_USED(env->cg); if (USE_SSE2(env->cg)) - return gen_binop(env, op1, op2, new_rd_ia32_fAdd); + return gen_binop(env, op1, op2, new_rd_ia32_xAdd); else return gen_binop(env, op1, op2, new_rd_ia32_vfadd); } @@ -532,7 +533,7 @@ static ir_node *gen_Mul(ia32_transform_env_t *env) { if (mode_is_float(env->mode)) { FP_USED(env->cg); if (USE_SSE2(env->cg)) - new_op = gen_binop(env, op1, op2, new_rd_ia32_fMul); + new_op = gen_binop(env, op1, op2, new_rd_ia32_xMul); else new_op = gen_binop(env, op1, op2, new_rd_ia32_vfmul); } @@ -648,7 +649,7 @@ static ir_node *gen_Max(ia32_transform_env_t *env) { if (mode_is_float(env->mode)) { FP_USED(env->cg); if (USE_SSE2(env->cg)) - new_op = gen_binop(env, op1, op2, new_rd_ia32_fMax); + new_op = gen_binop(env, op1, op2, new_rd_ia32_xMax); else { assert(0); } @@ -678,7 +679,7 @@ static ir_node *gen_Min(ia32_transform_env_t *env) { if (mode_is_float(env->mode)) { FP_USED(env->cg); if (USE_SSE2(env->cg)) - new_op = gen_binop(env, op1, op2, new_rd_ia32_fMin); + new_op = gen_binop(env, op1, op2, new_rd_ia32_xMin); else { assert(0); } @@ -768,7 +769,7 @@ static ir_node *gen_Sub(ia32_transform_env_t *env) { if (mode_is_float(mode)) { FP_USED(env->cg); if (USE_SSE2(env->cg)) - return gen_binop(env, op1, op2, new_rd_ia32_fSub); + return gen_binop(env, op1, op2, new_rd_ia32_xSub); else return gen_binop(env, op1, op2, new_rd_ia32_vfsub); } @@ -948,7 +949,7 @@ static ir_node *gen_DivMod(ia32_transform_env_t *env) { * Creates an ia32 floating Div. * * @param env The transformation environment - * @return The created ia32 fDiv node + * @return The created ia32 xDiv node */ static ir_node *gen_Quot(ia32_transform_env_t *env) { ir_node *noreg = ia32_new_NoReg_gp(env->cg); @@ -959,13 +960,13 @@ static ir_node *gen_Quot(ia32_transform_env_t *env) { FP_USED(env->cg); if (USE_SSE2(env->cg)) { - if (is_ia32_fConst(op2)) { - new_op = new_rd_ia32_fDiv(env->dbg, env->irg, env->block, noreg, noreg, op1, noreg, nomem, mode_T); + if (is_ia32_xConst(op2)) { + new_op = new_rd_ia32_xDiv(env->dbg, env->irg, env->block, noreg, noreg, op1, noreg, nomem, mode_T); set_ia32_am_support(new_op, ia32_am_None); set_ia32_Immop_attr(new_op, op2); } else { - new_op = new_rd_ia32_fDiv(env->dbg, env->irg, env->block, noreg, noreg, op1, op2, nomem, mode_T); + new_op = new_rd_ia32_xDiv(env->dbg, env->irg, env->block, noreg, noreg, op1, op2, nomem, mode_T); set_ia32_am_support(new_op, ia32_am_Source); } } @@ -1113,7 +1114,7 @@ static ir_node *gen_Minus_ex(ia32_transform_env_t *env, ir_node *op) { ir_node *noreg_fp = ia32_new_NoReg_fp(env->cg); ir_node *nomem = new_rd_NoMem(env->irg); - new_op = new_rd_ia32_fEor(env->dbg, env->irg, env->block, noreg_gp, noreg_gp, op, noreg_fp, nomem, mode_T); + new_op = new_rd_ia32_xEor(env->dbg, env->irg, env->block, noreg_gp, noreg_gp, op, noreg_fp, nomem, mode_T); size = get_mode_size_bits(env->mode); name = gen_fp_known_const(env->mode, size == 32 ? ia32_SSIGN : ia32_DSIGN); @@ -1185,7 +1186,7 @@ static ir_node *gen_Abs(ia32_transform_env_t *env) { if (mode_is_float(mode)) { FP_USED(env->cg); if (USE_SSE2(env->cg)) { - res = new_rd_ia32_fAnd(dbg,irg, block, noreg_gp, noreg_gp, op, noreg_fp, nomem, mode_T); + res = new_rd_ia32_xAnd(dbg,irg, block, noreg_gp, noreg_gp, op, noreg_fp, nomem, mode_T); size = get_mode_size_bits(mode); name = gen_fp_known_const(mode, size == 32 ? ia32_SABS : ia32_DABS); @@ -1255,7 +1256,7 @@ static ir_node *gen_Load(ia32_transform_env_t *env) { if (mode_is_float(mode)) { FP_USED(env->cg); if (USE_SSE2(env->cg)) - new_op = new_rd_ia32_fLoad(env->dbg, env->irg, env->block, lptr, noreg, get_Load_mem(node), env->mode); + new_op = new_rd_ia32_xLoad(env->dbg, env->irg, env->block, lptr, noreg, get_Load_mem(node), env->mode); else new_op = new_rd_ia32_vfld(env->dbg, env->irg, env->block, lptr, noreg, get_Load_mem(node), env->mode); } @@ -1333,7 +1334,7 @@ static ir_node *gen_Store(ia32_transform_env_t *env) { if (mode_is_float(mode)) { FP_USED(env->cg); if (USE_SSE2(env->cg)) - new_op = new_rd_ia32_fStore(env->dbg, env->irg, env->block, sptr, noreg, sval, mem, mode_T); + new_op = new_rd_ia32_xStore(env->dbg, env->irg, env->block, sptr, noreg, sval, mem, mode_T); else new_op = new_rd_ia32_vfst(env->dbg, env->irg, env->block, sptr, noreg, sval, mem, mode_T); } @@ -1439,7 +1440,7 @@ static ir_node *gen_Cond(ia32_transform_env_t *env) { if (mode_is_float(get_irn_mode(expr))) { FP_USED(env->cg); if (USE_SSE2(env->cg)) - res = new_rd_ia32_fCondJmp(dbg, irg, block, noreg, noreg, expr, noreg, nomem, mode_T); + res = new_rd_ia32_xCondJmp(dbg, irg, block, noreg, noreg, expr, noreg, nomem, mode_T); else { assert(0); } @@ -1454,7 +1455,7 @@ static ir_node *gen_Cond(ia32_transform_env_t *env) { if (mode_is_float(get_irn_mode(cmp_a))) { FP_USED(env->cg); if (USE_SSE2(env->cg)) - res = new_rd_ia32_fCondJmp(dbg, irg, block, noreg, noreg, cmp_a, cmp_b, nomem, mode_T); + res = new_rd_ia32_xCondJmp(dbg, irg, block, noreg, noreg, cmp_a, cmp_b, nomem, mode_T); else { assert(0); } @@ -1830,7 +1831,7 @@ static ir_node *gen_be_StackParam(ia32_transform_env_t *env) { if (mode_is_float(mode)) { FP_USED(env->cg); if (USE_SSE2(env->cg)) - new_op = new_rd_ia32_fLoad(env->dbg, env->irg, env->block, ptr, noreg, mem, mode_T); + new_op = new_rd_ia32_xLoad(env->dbg, env->irg, env->block, ptr, noreg, mem, mode_T); else new_op = new_rd_ia32_vfld(env->dbg, env->irg, env->block, ptr, noreg, mem, mode_T); } @@ -1888,7 +1889,7 @@ static ir_node *gen_be_FrameLoad(ia32_transform_env_t *env) { if (mode_is_float(mode)) { FP_USED(env->cg); if (USE_SSE2(env->cg)) - new_op = new_rd_ia32_fLoad(env->dbg, env->irg, env->block, ptr, noreg, mem, mode_T); + new_op = new_rd_ia32_xLoad(env->dbg, env->irg, env->block, ptr, noreg, mem, mode_T); else new_op = new_rd_ia32_vfld(env->dbg, env->irg, env->block, ptr, noreg, mem, mode_T); } @@ -1925,7 +1926,7 @@ static ir_node *gen_be_FrameStore(ia32_transform_env_t *env) { if (mode_is_float(mode)) { FP_USED(env->cg); if (USE_SSE2(env->cg)) - new_op = new_rd_ia32_fStore(env->dbg, env->irg, env->block, ptr, noreg, val, mem, mode_T); + new_op = new_rd_ia32_xStore(env->dbg, env->irg, env->block, ptr, noreg, val, mem, mode_T); else new_op = new_rd_ia32_vfst(env->dbg, env->irg, env->block, ptr, noreg, val, mem, mode_T); } @@ -1986,7 +1987,7 @@ static ir_node *gen_Unknown(ia32_transform_env_t *env) { *********************************************************/ /** - * Transforms a Sub or fSub into Neg--Add iff OUT_REG == SRC2_REG. + * Transforms a Sub or xSub into Neg--Add iff OUT_REG == SRC2_REG. * THIS FUNCTIONS MUST BE CALLED AFTER REGISTER ALLOCATION. */ void ia32_transform_sub_to_neg_add(ir_node *irn, ia32_code_gen_t *cg) { @@ -1994,8 +1995,8 @@ void ia32_transform_sub_to_neg_add(ir_node *irn, ia32_code_gen_t *cg) { ir_node *in1, *in2, *noreg, *nomem, *res; const arch_register_t *in1_reg, *in2_reg, *out_reg, **slots; - /* Return if AM node or not a Sub or fSub */ - if (get_ia32_op_type(irn) != ia32_Normal || !(is_ia32_Sub(irn) || is_ia32_fSub(irn))) + /* Return if AM node or not a Sub or xSub */ + if (get_ia32_op_type(irn) != ia32_Normal || !(is_ia32_Sub(irn) || is_ia32_xSub(irn))) return; noreg = ia32_new_NoReg_gp(cg); @@ -2025,7 +2026,7 @@ void ia32_transform_sub_to_neg_add(ir_node *irn, ia32_code_gen_t *cg) { /* generate the add */ if (mode_is_float(tenv.mode)) { - res = new_rd_ia32_fAdd(tenv.dbg, tenv.irg, tenv.block, noreg, noreg, res, in1, nomem, mode_T); + res = new_rd_ia32_xAdd(tenv.dbg, tenv.irg, tenv.block, noreg, noreg, res, in1, nomem, mode_T); set_ia32_am_support(res, ia32_am_Source); } else { @@ -2045,6 +2046,8 @@ void ia32_transform_sub_to_neg_add(ir_node *irn, ia32_code_gen_t *cg) { /* remove the old sub */ sched_remove(irn); + DBG_OPT_SUB2NEGADD(irn, res); + /* exchange the add and the sub */ exchange(irn, res); } @@ -2155,6 +2158,8 @@ void ia32_transform_lea_to_add(ir_node *irn, ia32_code_gen_t *cg) { /* add Add to schedule */ sched_add_before(irn, res); + DBG_OPT_LEA2ADD(irn, res); + res = new_rd_Proj(tenv.dbg, tenv.irg, tenv.block, res, tenv.mode, 0); /* add result Proj to schedule */ -- 2.20.1