From eb97d02405c08bd0c006c8d65b3649fbfb1148e0 Mon Sep 17 00:00:00 2001 From: Alan Modra Date: Sat, 11 Dec 2021 14:52:36 +1030 Subject: [GOLD] PowerPC64 @notoc in non-power10 code Gold version of commit 7aba54da42. elfcpp/ * powerpc.h (R_PPC64_REL24_P9NOTOC): Define. gold/ * powerpc.cc (Target_powerpc::maybe_skip_tls_get_addr_call, is_branch_reloc, max_branch_delta): Handle R_PPC64_REL24_P9NOTOC. (Target_powerpc::Branch_info::make_stub): Likewise. (struct Plt_stub_ent): Add p9notoc_, p9off_, tsize_. (struct Branch_stub_ent): Add p9notoc_, p9off_. (Stub_table::add_plt_call_entry): Handle R_PPC64_REL24_P9NOTOC. (Stub_table::add_long_branch_entry): Likewise. (Stub_table::add_eh_frame): Likewise. (Stub_table::plt_call_size): Return aligned size. Adjust callers. Handle p9notoc_ sizing. (Stub_table::do_write): Write out p9notoc_ stubs. (Target_powerpc::Scan::get_reference_flags, local, global): Handle R_PPC64_REL24_P9NOTOC. (Target_powerpc::Relocate::relocate): Likewise. --- gold/powerpc.cc | 298 +++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 222 insertions(+), 76 deletions(-) (limited to 'gold') diff --git a/gold/powerpc.cc b/gold/powerpc.cc index 103eef8594..a40614cd45 100644 --- a/gold/powerpc.cc +++ b/gold/powerpc.cc @@ -1329,6 +1329,7 @@ class Target_powerpc : public Sized_target bool is_tls_call = ((r_type == elfcpp::R_POWERPC_REL24 || (size == 64 && r_type == elfcpp::R_PPC64_REL24_NOTOC) + || r_type == elfcpp::R_PPC64_REL24_P9NOTOC || r_type == elfcpp::R_PPC_PLTREL24 || is_plt16_reloc(r_type) || r_type == elfcpp::R_PPC64_PLT_PCREL34 @@ -1891,6 +1892,7 @@ is_branch_reloc(unsigned int r_type) { return (r_type == elfcpp::R_POWERPC_REL24 || (size == 64 && r_type == elfcpp::R_PPC64_REL24_NOTOC) + || r_type == elfcpp::R_PPC64_REL24_P9NOTOC || r_type == elfcpp::R_PPC_PLTREL24 || r_type == elfcpp::R_PPC_LOCAL24PC || r_type == elfcpp::R_POWERPC_REL14 @@ -3447,6 +3449,7 @@ max_branch_delta (unsigned int r_type) return 1L << 15; if (r_type == elfcpp::R_POWERPC_REL24 || (size == 64 && r_type == elfcpp::R_PPC64_REL24_NOTOC) + || r_type == elfcpp::R_PPC64_REL24_P9NOTOC || r_type == elfcpp::R_PPC_PLTREL24 || r_type == elfcpp::R_PPC_LOCAL24PC) return 1L << 25; @@ -3621,12 +3624,14 @@ Target_powerpc::Branch_info::make_stub( } unsigned int local_ent = 0; if (size == 64 - && this->r_type_ != elfcpp::R_PPC64_REL24_NOTOC) + && this->r_type_ != elfcpp::R_PPC64_REL24_NOTOC + && this->r_type_ != elfcpp::R_PPC64_REL24_P9NOTOC) local_ent = elfcpp::ppc64_decode_local_entry(other); Address delta = to + local_ent - from; if (delta + max_branch_offset >= 2 * max_branch_offset || (size == 64 - && this->r_type_ == elfcpp::R_PPC64_REL24_NOTOC + && (this->r_type_ == elfcpp::R_PPC64_REL24_NOTOC + || this->r_type_ == elfcpp::R_PPC64_REL24_P9NOTOC) && (gsym != NULL ? this->object_->ppc64_needs_toc(gsym) : this->object_->ppc64_needs_toc(this->r_sym_)))) @@ -4777,33 +4782,50 @@ class Stub_table : public Output_relaxed_input_section struct Plt_stub_ent { Plt_stub_ent(unsigned int off, unsigned int indx) - : off_(off), indx_(indx), iter_(0), notoc_(0), toc_(0), - r2save_(0), localentry0_(0), tocoff_(0) + : off_(off), indx_(indx), tocoff_(0), p9off_(0), tsize_ (0), iter_(0), + toc_(0), notoc_(0), p9notoc_(0), r2save_(0), localentry0_(0) { } unsigned int off_; unsigned int indx_; + // off_ points at p10 notoc stub, tocoff_ is offset from there to + // toc stub, p9off_ is offset to p9notoc stub + unsigned int tocoff_ : 8; + unsigned int p9off_ : 8; + // The size of the toc stub, used to locate blr on tls_get_addr stub. + unsigned int tsize_ : 8; + // Stub revision management unsigned int iter_ : 1; - unsigned int notoc_ : 1; + // The three types of stubs. unsigned int toc_ : 1; + unsigned int notoc_ : 1; + unsigned int p9notoc_ : 1; + // Each with a possible variant saving r2 first unsigned int r2save_ : 1; + // Handy cached info from symbol unsigned int localentry0_ : 1; - unsigned int tocoff_ : 8; }; struct Branch_stub_ent { - Branch_stub_ent(unsigned int off, bool notoc, bool save_res) - : off_(off), iter_(0), notoc_(notoc), toc_(0), save_res_(save_res), - other_(0), tocoff_(0) + Branch_stub_ent(unsigned int off) + : off_(off), tocoff_(0), p9off_(0), iter_(0), toc_(0), notoc_(0), + p9notoc_(0), save_res_(0), other_(0) { } unsigned int off_; + // off_ points at p10 notoc stub, tocoff_ is offset from there to + // toc stub, p9off_ is offset to p9notoc stub + unsigned int tocoff_ : 8; + unsigned int p9off_ : 8; + // Stub revision management unsigned int iter_ : 1; - unsigned int notoc_ : 1; + // Four types of stubs. unsigned int toc_ : 1; + unsigned int notoc_ : 1; + unsigned int p9notoc_ : 1; unsigned int save_res_ : 1; + // Handy cached info from symbol unsigned int other_ : 3; - unsigned int tocoff_ : 8; }; typedef typename elfcpp::Elf_types::Elf_Addr Address; static const Address invalid_address = static_cast
(0) - 1; @@ -5223,13 +5245,23 @@ Stub_table::add_plt_call_entry( p.first->second.localentry0_ = 1; this->targ_->set_has_localentry0(); } - if (r_type == elfcpp::R_PPC64_REL24_NOTOC) + if (r_type == elfcpp::R_PPC64_REL24_NOTOC + || r_type == elfcpp::R_PPC64_REL24_P9NOTOC) { - if (!p.second && !p.first->second.notoc_ - && (!this->targ_->power10_stubs() - || this->targ_->power10_stubs_auto())) - this->need_resize_ = true; - p.first->second.notoc_ = 1; + if (this->targ_->power10_stubs() + && (!this->targ_->power10_stubs_auto() + || r_type == elfcpp::R_PPC64_REL24_NOTOC)) + { + if (!p.second && !p.first->second.notoc_) + this->need_resize_ = true; + p.first->second.notoc_ = 1; + } + else + { + if (!p.second && !p.first->second.p9notoc_) + this->need_resize_ = true; + p.first->second.p9notoc_ = 1; + } } else { @@ -5254,7 +5286,6 @@ Stub_table::add_plt_call_entry( this->plt_size_ += this->plt_call_size(p.first); if (this->targ_->is_tls_get_addr_opt(gsym)) this->targ_->set_has_tls_get_addr_opt(); - this->plt_size_ = this->plt_call_align(this->plt_size_); } return this->can_reach_stub(from, p.first->second.off_, r_type); } @@ -5281,13 +5312,23 @@ Stub_table::add_plt_call_entry( p.first->second.localentry0_ = 1; this->targ_->set_has_localentry0(); } - if (r_type == elfcpp::R_PPC64_REL24_NOTOC) + if (r_type == elfcpp::R_PPC64_REL24_NOTOC + || r_type == elfcpp::R_PPC64_REL24_P9NOTOC) { - if (!p.second && !p.first->second.notoc_ - && (!this->targ_->power10_stubs() - || this->targ_->power10_stubs_auto())) - this->need_resize_ = true; - p.first->second.notoc_ = 1; + if (this->targ_->power10_stubs() + && (!this->targ_->power10_stubs_auto() + || r_type == elfcpp::R_PPC64_REL24_NOTOC)) + { + if (!p.second && !p.first->second.notoc_) + this->need_resize_ = true; + p.first->second.notoc_ = 1; + } + else + { + if (!p.second && !p.first->second.p9notoc_) + this->need_resize_ = true; + p.first->second.p9notoc_ = 1; + } } else { @@ -5310,7 +5351,6 @@ Stub_table::add_plt_call_entry( p.first->second.off_ = this->plt_size_; } this->plt_size_ += this->plt_call_size(p.first); - this->plt_size_ = this->plt_call_align(this->plt_size_); } return this->can_reach_stub(from, p.first->second.off_, r_type); } @@ -5384,15 +5424,33 @@ Stub_table::add_long_branch_entry( bool save_res) { Branch_stub_key key(to); - bool notoc = (size == 64 && r_type == elfcpp::R_PPC64_REL24_NOTOC); - Branch_stub_ent ent(this->branch_size_, notoc, save_res); + Branch_stub_ent ent(this->branch_size_); std::pair p = this->long_branch_stubs_.insert(std::make_pair(key, ent)); - if (notoc) + if (save_res) { - if (!p.second && !p.first->second.notoc_) + if (!p.second && !p.first->second.save_res_) this->need_resize_ = true; - p.first->second.notoc_ = true; + p.first->second.save_res_ = true; + } + else if (size == 64 + && (r_type == elfcpp::R_PPC64_REL24_NOTOC + || r_type == elfcpp::R_PPC64_REL24_P9NOTOC)) + { + if (this->targ_->power10_stubs() + && (!this->targ_->power10_stubs_auto() + || r_type == elfcpp::R_PPC64_REL24_NOTOC)) + { + if (!p.second && !p.first->second.notoc_) + this->need_resize_ = true; + p.first->second.notoc_ = true; + } + else + { + if (!p.second && !p.first->second.p9notoc_) + this->need_resize_ = true; + p.first->second.p9notoc_ = true; + } } else { @@ -5402,7 +5460,6 @@ Stub_table::add_long_branch_entry( } if (size == 64 && p.first->second.other_ == 0) p.first->second.other_ = other; - gold_assert(save_res == p.first->second.save_res_); if (p.second || (this->resizing_ && !p.first->second.iter_)) { if (this->resizing_) @@ -5500,11 +5557,11 @@ Stub_table::add_eh_frame(Layout* layout) for (plt_iter cs = this->plt_call_stubs_.begin(); cs != this->plt_call_stubs_.end(); ++cs) - if ((this->targ_->is_tls_get_addr_opt(cs->first.sym_) - && cs->second.r2save_ - && !cs->second.localentry0_) - || (cs->second.notoc_ - && !this->targ_->power10_stubs())) + if (cs->second.p9notoc_ + || (cs->second.toc_ + && cs->second.r2save_ + && !cs->second.localentry0_ + && this->targ_->is_tls_get_addr_opt(cs->first.sym_))) calls.push_back(cs); if (calls.size() > 1) std::stable_sort(calls.begin(), calls.end(), @@ -5540,37 +5597,42 @@ Stub_table::add_eh_frame(Layout* layout) if (this->targ_->is_tls_get_addr_opt(cs->first.sym_)) { off += 7 * 4; - if (cs->second.r2save_ + if (cs->second.toc_ + && cs->second.r2save_ && !cs->second.localentry0_) { - off += 2 * 4; + off += cs->second.tocoff_ + 2 * 4; eh_advance(fde, off - last_eh_loc); fde.resize(fde.size() + 6); unsigned char* p = &*fde.end() - 6; *p++ = elfcpp::DW_CFA_offset_extended_sf; *p++ = 65; *p++ = -(this->targ_->stk_linker() / 8) & 0x7f; - unsigned int delta = this->plt_call_size(cs) - 4 - 9 * 4; + unsigned int delta = cs->second.tsize_ - 9 * 4 - 4; *p++ = elfcpp::DW_CFA_advance_loc + delta / 4; *p++ = elfcpp::DW_CFA_restore_extended; *p++ = 65; last_eh_loc = off + delta; - continue; + off = cs->second.off_ + 7 * 4; } } // notoc stubs also should describe LR changes, to support // asynchronous exceptions. - off += (cs->second.r2save_ ? 4 : 0) + 8; - eh_advance(fde, off - last_eh_loc); - fde.resize(fde.size() + 6); - unsigned char* p = &*fde.end() - 6; - *p++ = elfcpp::DW_CFA_register; - *p++ = 65; - *p++ = 12; - *p++ = elfcpp::DW_CFA_advance_loc + 8 / 4; - *p++ = elfcpp::DW_CFA_restore_extended; - *p++ = 65; - last_eh_loc = off + 8; + if (cs->second.p9notoc_) + { + off += cs->second.p9off_; + off += (cs->second.r2save_ ? 4 : 0) + 8; + eh_advance(fde, off - last_eh_loc); + fde.resize(fde.size() + 6); + unsigned char* p = &*fde.end() - 6; + *p++ = elfcpp::DW_CFA_register; + *p++ = 65; + *p++ = 12; + *p++ = elfcpp::DW_CFA_advance_loc + 8 / 4; + *p++ = elfcpp::DW_CFA_restore_extended; + *p++ = 65; + last_eh_loc = off + 8; + } } for (unsigned int i = 0; i < branches.size(); i++) @@ -5842,7 +5904,7 @@ Stub_table::define_stub_syms(Symbol_table* symtab) sprintf(name, "%08x.plt_call.%s%s%s", this->uniq_, obj, symname, add); Address value = this->stub_address() - this->address() + cs->second.off_; - unsigned int stub_size = this->plt_call_align(this->plt_call_size(cs)); + unsigned int stub_size = this->plt_call_size(cs); this->targ_->define_local(symtab, name, this, value, stub_size); } } @@ -6108,9 +6170,10 @@ Stub_table::plt_call_size( { if (size == 32) { - const Symbol* gsym = p->first.sym_; - return (4 * 4 - + (this->targ_->is_tls_get_addr_opt(gsym) ? 8 * 4 : 0)); + unsigned int bytes = 4 * 4; + if (this->targ_->is_tls_get_addr_opt(p->first.sym_)) + bytes = 12 * 4; + return this->plt_call_align(bytes); } const Output_data_plt_powerpc* plt; @@ -6135,7 +6198,6 @@ Stub_table::plt_call_size( bytes += 8 * 4; bytes = this->plt_call_align(bytes); } - unsigned int tail = 0; if (p->second.toc_) { p->second.tocoff_ = bytes; @@ -6143,18 +6205,43 @@ Stub_table::plt_call_size( { bytes += 7 * 4; if (p->second.r2save_ && !p->second.localentry0_) - { - bytes += 2 * 4; - tail = 4 * 4; - } + bytes += 2 * 4 + 4 * 4; } if (p->second.r2save_) bytes += 4; uint64_t got_addr = this->targ_->toc_pointer(); uint64_t off = plt_addr - got_addr; bytes += 3 * 4 + 4 * (ha(off) != 0); + p->second.tsize_ = bytes - p->second.tocoff_; + bytes = this->plt_call_align(bytes); } - return bytes + tail; + if (p->second.p9notoc_) + { + p->second.p9off_ = bytes; + if (this->targ_->is_tls_get_addr_opt(p->first.sym_)) + bytes += 7 * 4; + uint64_t from = this->stub_address() + p->second.off_ + bytes + 2 * 4; + uint64_t off = plt_addr - from; + if (off + 0x8000 < 0x10000) + bytes += 7 * 4; + else if (off + 0x80008000ULL < 0x100000000ULL) + bytes += 8 * 4; + else + { + bytes += 8 * 4; + if (off + 0x800000000000ULL >= 0x1000000000000ULL + && ((off >> 32) & 0xffff) != 0) + bytes += 4; + if (((off >> 32) & 0xffffffffULL) != 0) + bytes += 4; + if (hi(off) != 0) + bytes += 4; + if (l(off) != 0) + bytes += 4; + } + bytes = this->plt_call_align(bytes); + } + return bytes; } else { @@ -6184,10 +6271,10 @@ Stub_table::plt_call_size( bytes += 7 * 4; else bytes += 8 * 4; - return bytes + tail; + return this->plt_call_align(bytes + tail); } - if (p->second.notoc_) + if (p->second.p9notoc_) { uint64_t from = this->stub_address() + p->second.off_ + bytes + 2 * 4; uint64_t off = plt_addr - from; @@ -6208,7 +6295,7 @@ Stub_table::plt_call_size( if (l(off) != 0) bytes += 4; } - return bytes + tail; + return this->plt_call_align(bytes + tail); } uint64_t got_addr = this->targ_->toc_pointer(); @@ -6223,7 +6310,7 @@ Stub_table::plt_call_size( + 8 * thread_safe + 4 * (ha(off + 8 + 8 * static_chain) != ha(off))); } - return bytes + tail; + return this->plt_call_align(bytes + tail); } } @@ -6400,6 +6487,19 @@ Stub_table::do_write(Output_file* of) else write_insn(p, bctr); } + if (cs->second.p9notoc_) + { + if (this->targ_->is_tls_get_addr_opt(cs->first.sym_)) + this->build_tls_opt_head(&p, false); + Address from = this->stub_address() + (p - oview); + Address delta = plt_addr - from; + p = build_notoc_offset(p, delta, true); + write_insn(p, mtctr_12); + p += 4; + write_insn(p, bctr); + p += 4; + p = oview + this->plt_call_align(p - oview); + } } else { @@ -6494,6 +6594,23 @@ Stub_table::do_write(Output_file* of) write_insn(p, bctr); } } + if (bs->second.p9notoc_) + { + unsigned char* startp = p; + p = build_notoc_offset(p, delta, false); + delta -= p - startp; + startp = p; + if (delta + (1 << 25) < 2 << 25) + write_insn(p, b | (delta & 0x3fffffc)); + else + { + write_insn(p, mtctr_12); + p += 4; + write_insn(p, bctr); + } + p += 4; + delta -= p - startp; + } } else { @@ -6544,7 +6661,7 @@ Stub_table::do_write(Output_file* of) write_insn(p, std_2_1 + this->targ_->stk_toc()); p += 4; } - if (cs->second.notoc_) + if (cs->second.p9notoc_) { Address from = this->stub_address() + (p - oview) + 8; Address off = plt_addr - from; @@ -6722,9 +6839,9 @@ Stub_table::do_write(Output_file* of) p = oview + off; Address loc = this->stub_address() + off; Address delta = bs->first.dest_ - loc; - if (!bs->second.notoc_) + if (!bs->second.p9notoc_) delta += elfcpp::ppc64_decode_local_entry(bs->second.other_); - if (bs->second.notoc_) + if (bs->second.p9notoc_) { unsigned char* startp = p; p = build_notoc_offset(p, off, false); @@ -7619,6 +7736,7 @@ Target_powerpc::Scan::get_reference_flags( if (size == 32) break; // Fall through. + case elfcpp::R_PPC64_REL24_P9NOTOC: case elfcpp::R_POWERPC_REL24: case elfcpp::R_PPC_PLTREL24: case elfcpp::R_POWERPC_REL14: @@ -7861,6 +7979,7 @@ Target_powerpc::Scan::reloc_needs_plt_for_ifunc( if (size == 32) break; // Fall through. + case elfcpp::R_PPC64_REL24_P9NOTOC: case elfcpp::R_POWERPC_ADDR24: case elfcpp::R_POWERPC_ADDR14: case elfcpp::R_POWERPC_ADDR14_BRTAKEN: @@ -8124,6 +8243,7 @@ Target_powerpc::Scan::local( if (size == 32) break; // Fall through. + case elfcpp::R_PPC64_REL24_P9NOTOC: case elfcpp::R_POWERPC_REL24: case elfcpp::R_PPC_PLTREL24: case elfcpp::R_PPC_LOCAL24PC: @@ -8873,6 +8993,7 @@ Target_powerpc::Scan::global( if (size == 32) break; // Fall through. + case elfcpp::R_PPC64_REL24_P9NOTOC: case elfcpp::R_PPC_PLTREL24: case elfcpp::R_POWERPC_REL24: if (!is_ifunc) @@ -10630,15 +10751,25 @@ Target_powerpc::Relocate::relocate( const int reloc_size = elfcpp::Elf_sizes::rela_size; elfcpp::Shdr shdr(relinfo->reloc_shdr); size_t reloc_count = shdr.get_sh_size() / reloc_size; - if (size == 64 - && r_type != elfcpp::R_PPC64_REL24_NOTOC) - value += ent->tocoff_; + if (size == 64) + { + if (r_type == elfcpp::R_PPC64_REL24_NOTOC) + { + if (!ent->notoc_) + value += ent->p9off_; + } + else if (r_type == elfcpp::R_PPC64_REL24_P9NOTOC) + value += ent->p9off_; + else + value += ent->tocoff_; + } if (size == 64 && ent->r2save_ && !(gsym != NULL && target->is_tls_get_addr_opt(gsym))) { - if (r_type == elfcpp::R_PPC64_REL24_NOTOC) + if (r_type == elfcpp::R_PPC64_REL24_NOTOC + || r_type == elfcpp::R_PPC64_REL24_P9NOTOC) { if (!(target->power10_stubs() && target->power10_stubs_auto())) @@ -11078,6 +11209,7 @@ Target_powerpc::Relocate::relocate( unsigned int r_type2 = elfcpp::elf_r_type(next_rela.get_r_info()); if ((r_type2 == elfcpp::R_PPC64_REL24_NOTOC + || r_type2 == elfcpp::R_PPC64_REL24_P9NOTOC || r_type2 == elfcpp::R_PPC64_PLTCALL_NOTOC) && next_rela.get_r_offset() == rela.get_r_offset()) is_pcrel = true; @@ -11118,6 +11250,7 @@ Target_powerpc::Relocate::relocate( unsigned int r_type2 = elfcpp::elf_r_type(next_rela.get_r_info()); if ((r_type2 == elfcpp::R_PPC64_REL24_NOTOC + || r_type2 == elfcpp::R_PPC64_REL24_P9NOTOC || r_type2 == elfcpp::R_PPC64_PLTCALL_NOTOC) && next_rela.get_r_offset() == rela.get_r_offset()) is_pcrel = true; @@ -11227,7 +11360,8 @@ Target_powerpc::Relocate::relocate( if (max_branch != 0 && (value + local_ent - address + max_branch >= 2 * max_branch || (size == 64 - && r_type == elfcpp::R_PPC64_REL24_NOTOC + && (r_type == elfcpp::R_PPC64_REL24_NOTOC + || r_type == elfcpp::R_PPC64_REL24_NOTOC) && (gsym != NULL ? object->ppc64_needs_toc(gsym) : object->ppc64_needs_toc(r_sym))))) @@ -11250,9 +11384,18 @@ Target_powerpc::Relocate::relocate( value = (stub_table->stub_address() + stub_table->plt_size() + ent->off_); - if (size == 64 - && r_type != elfcpp::R_PPC64_REL24_NOTOC) - value += ent->tocoff_; + if (size == 64) + { + if (r_type == elfcpp::R_PPC64_REL24_NOTOC) + { + if (!ent->notoc_) + value += ent->p9off_; + } + else if (r_type == elfcpp::R_PPC64_REL24_P9NOTOC) + value += ent->p9off_; + else + value += ent->tocoff_; + } } has_stub_value = true; } @@ -11268,6 +11411,7 @@ Target_powerpc::Relocate::relocate( if (size == 32) break; // Fall through. + case elfcpp::R_PPC64_REL24_P9NOTOC: case elfcpp::R_PPC64_REL64: case elfcpp::R_POWERPC_REL32: case elfcpp::R_POWERPC_REL24: @@ -11802,6 +11946,7 @@ Target_powerpc::Relocate::relocate( if (size == 32) break; // Fall through. + case elfcpp::R_PPC64_REL24_P9NOTOC: case elfcpp::R_POWERPC_ADDR24: case elfcpp::R_POWERPC_ADDR14: case elfcpp::R_POWERPC_ADDR14_BRTAKEN: @@ -11905,6 +12050,7 @@ Target_powerpc::Relocate::relocate( if (size == 32) goto unsupp; // R_PPC_EMB_RELSDA // Fall through. + case elfcpp::R_PPC64_REL24_P9NOTOC: case elfcpp::R_POWERPC_ADDR24: case elfcpp::R_POWERPC_REL24: case elfcpp::R_PPC_PLTREL24: -- cgit v1.2.3