| ;; Machine description for LoongArch atomic operations. |
| ;; Copyright (C) 2021-2026 Free Software Foundation, Inc. |
| ;; Contributed by Loongson Ltd. |
| ;; Based on MIPS and RISC-V target for GNU compiler. |
| |
| ;; This file is part of GCC. |
| |
| ;; GCC is free software; you can redistribute it and/or modify |
| ;; it under the terms of the GNU General Public License as published by |
| ;; the Free Software Foundation; either version 3, or (at your option) |
| ;; any later version. |
| |
| ;; GCC is distributed in the hope that it will be useful, |
| ;; but WITHOUT ANY WARRANTY; without even the implied warranty of |
| ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| ;; GNU General Public License for more details. |
| |
| ;; You should have received a copy of the GNU General Public License |
| ;; along with GCC; see the file COPYING3. If not see |
| ;; <http://www.gnu.org/licenses/>. |
| |
| (define_c_enum "unspec" [ |
| UNSPEC_COMPARE_AND_SWAP |
| UNSPEC_COMPARE_AND_SWAP_AMCAS |
| UNSPEC_COMPARE_AND_SWAP_ADD |
| UNSPEC_COMPARE_AND_SWAP_SUB |
| UNSPEC_COMPARE_AND_SWAP_NAND |
| UNSPEC_SYNC_OLD_OP |
| UNSPEC_SYNC_EXCHANGE |
| UNSPEC_ATOMIC_STORE |
| UNSPEC_ATOMIC_LOAD |
| UNSPEC_MEMORY_BARRIER |
| |
| UNSPEC_TI_FETCH_ADD |
| UNSPEC_TI_FETCH_SUB |
| UNSPEC_TI_FETCH_AND |
| UNSPEC_TI_FETCH_XOR |
| UNSPEC_TI_FETCH_OR |
| UNSPEC_TI_FETCH_NAND_MASK_INVERTED |
| ]) |
| |
| (define_code_iterator any_atomic [plus ior xor and]) |
| |
| ;; <amop> expands to the name of the atomic operand that implements a |
| ;; particular code. |
| (define_code_attr amop [(ior "or") (xor "xor") (and "and") (plus "add")]) |
| |
| ;; For 32 bit. |
| (define_code_attr atomic_optab_insn |
| [(plus "add.w") (ior "or") (xor "xor") (and "and")]) |
| |
| ;; Memory barriers. |
| |
| (define_expand "mem_thread_fence" |
| [(match_operand:SI 0 "const_int_operand" "")] ;; model |
| "" |
| { |
| rtx mem = gen_rtx_MEM (BLKmode, gen_rtx_SCRATCH (Pmode)); |
| MEM_VOLATILE_P (mem) = 1; |
| emit_insn (gen_mem_thread_fence_1 (mem, operands[0])); |
| |
| DONE; |
| }) |
| |
| ;; DBAR hint encoding for LA664 and later micro-architectures, paraphrased from |
| ;; the Linux patch revealing it [1]: |
| ;; |
| ;; - Bit 4: kind of constraint (0: completion, 1: ordering) |
| ;; - Bit 3: barrier for previous read (0: true, 1: false) |
| ;; - Bit 2: barrier for previous write (0: true, 1: false) |
| ;; - Bit 1: barrier for succeeding read (0: true, 1: false) |
| ;; - Bit 0: barrier for succeeding write (0: true, 1: false) |
| ;; |
| ;; [1]: https://git.kernel.org/torvalds/c/e031a5f3f1ed |
| ;; |
| ;; Implementations without support for the finer-granularity hints simply treat |
| ;; all as the full barrier (DBAR 0), so we can unconditionally start emiting the |
| ;; more precise hints right away. |
| (define_insn "mem_thread_fence_1" |
| [(set (match_operand:BLK 0 "" "") |
| (unspec:BLK [(match_dup 0)] UNSPEC_MEMORY_BARRIER)) |
| (match_operand:SI 1 "const_int_operand" "")] ;; model |
| "" |
| { |
| enum memmodel model = memmodel_base (INTVAL (operands[1])); |
| |
| switch (model) |
| { |
| case MEMMODEL_ACQUIRE: |
| return "dbar\t0b10100"; |
| case MEMMODEL_RELEASE: |
| return "dbar\t0b10010"; |
| case MEMMODEL_ACQ_REL: |
| case MEMMODEL_SEQ_CST: |
| return "dbar\t0b10000"; |
| default: |
| /* GCC internal: "For the '__ATOMIC_RELAXED' model no instructions |
| need to be issued and this expansion is not invoked." |
| |
| __atomic builtins doc: "Consume is implemented using the |
| stronger acquire memory order because of a deficiency in C++11's |
| semantics." See PR 59448 and get_memmodel in builtins.cc. |
| |
| Other values should not be returned by memmodel_base. */ |
| gcc_unreachable (); |
| } |
| }) |
| |
| ;; Atomic memory operations. |
| |
| (define_insn "atomic_load<mode>" |
| [(set (match_operand:QHWD 0 "register_operand" "=r") |
| (unspec_volatile:QHWD |
| [(match_operand:QHWD 1 "memory_operand" "m") |
| (match_operand:SI 2 "const_int_operand")] ;; model |
| UNSPEC_ATOMIC_LOAD))] |
| "" |
| { |
| enum memmodel model = memmodel_base (INTVAL (operands[2])); |
| |
| switch (model) |
| { |
| case MEMMODEL_SEQ_CST: |
| return "dbar\t0x11\\n\\t" |
| "ld.<size>\t%0,%1\\n\\t" |
| "dbar\t0x14"; |
| case MEMMODEL_ACQUIRE: |
| return "ld.<size>\t%0,%1\\n\\t" |
| "dbar\t0x14"; |
| case MEMMODEL_RELAXED: |
| return ISA_HAS_LD_SEQ_SA ? "ld.<size>\t%0,%1" |
| : "ld.<size>\t%0,%1\\n\\t" |
| "dbar\t0x700"; |
| |
| default: |
| /* The valid memory order variants are __ATOMIC_RELAXED, __ATOMIC_SEQ_CST, |
| __ATOMIC_CONSUME and __ATOMIC_ACQUIRE. |
| The expand_builtin_atomic_store function converts all invalid memmodels |
| to MEMMODEL_SEQ_CST. |
| |
| __atomic builtins doc: "Consume is implemented using the |
| stronger acquire memory order because of a deficiency in C++11's |
| semantics." See PR 59448 and get_memmodel in builtins.cc. */ |
| gcc_unreachable (); |
| } |
| } |
| [(set (attr "length") (const_int 12))]) |
| |
| (define_insn "atomic_loadti_lsx" |
| [(set (match_operand:V2DI 0 "register_operand" "=f") |
| (unspec_volatile:V2DI |
| [(match_operand:TI 1 "memory_operand" "m") |
| (match_operand:SI 2 "const_int_operand")] ;; model |
| UNSPEC_ATOMIC_LOAD))] |
| "ISA_HAS_LSX && TARGET_64BIT" |
| { |
| enum memmodel model = memmodel_base (INTVAL (operands[2])); |
| |
| switch (model) |
| { |
| case MEMMODEL_SEQ_CST: |
| output_asm_insn ("dbar\t0x11", operands); |
| /* fall through */ |
| case MEMMODEL_ACQUIRE: |
| case MEMMODEL_RELAXED: |
| return "vld\t%w0,%1\\n\\t%G2"; |
| |
| default: |
| gcc_unreachable (); |
| } |
| } |
| [(set (attr "length") (const_int 12))]) |
| |
| (define_expand "atomic_loadti" |
| [(match_operand:TI 0 "register_operand" "=r") |
| (match_operand:TI 1 "memory_operand" "m") |
| (match_operand:SI 2 "const_int_operand")] |
| "ISA_HAS_LSX && TARGET_64BIT" |
| { |
| rtx vr = gen_reg_rtx (V2DImode); |
| |
| emit_insn (gen_atomic_loadti_lsx (vr, operands[1], operands[2])); |
| for (int i = 0; i < 2; i++) |
| emit_insn ( |
| gen_lsx_vpickve2gr_d (loongarch_subword (operands[0], i), vr, |
| GEN_INT (i))); |
| DONE; |
| }) |
| |
| ;; Implement atomic stores with amoswap. Fall back to fences for atomic loads. |
| (define_insn "atomic_store<mode>" |
| [(set (match_operand:QHWD 0 "memory_operand" "=m") |
| (unspec_volatile:QHWD |
| [(match_operand:QHWD 1 "reg_or_0_operand" "rJ") |
| (match_operand:SI 2 "const_int_operand")] ;; model |
| UNSPEC_ATOMIC_STORE))] |
| "" |
| { |
| enum memmodel model = memmodel_base (INTVAL (operands[2])); |
| |
| switch (model) |
| { |
| case MEMMODEL_SEQ_CST: |
| return "dbar\t0x12\\n\\t" |
| "st.<size>\t%z1,%0\\n\\t" |
| "dbar\t0x18\\n\\t"; |
| case MEMMODEL_RELEASE: |
| return "dbar\t0x12\\n\\t" |
| "st.<size>\t%z1,%0\\n\\t"; |
| case MEMMODEL_RELAXED: |
| return "st.<size>\t%z1,%0"; |
| |
| default: |
| /* The valid memory order variants are __ATOMIC_RELAXED, __ATOMIC_SEQ_CST, |
| and __ATOMIC_RELEASE. |
| The expand_builtin_atomic_store function converts all invalid memmodels |
| to MEMMODEL_SEQ_CST. */ |
| gcc_unreachable (); |
| } |
| } |
| [(set (attr "length") (const_int 12))]) |
| |
| (define_insn "atomic_storeti_lsx" |
| [(set (match_operand:TI 0 "memory_operand" "=m") |
| (unspec_volatile:TI |
| [(match_operand:V2DI 1 "register_operand" "f") |
| (match_operand:SI 2 "const_int_operand")] ;; model |
| UNSPEC_ATOMIC_STORE))] |
| "loongarch_16b_atomic_lock_free_p ()" |
| { |
| enum memmodel model = memmodel_base (INTVAL (operands[2])); |
| |
| switch (model) |
| { |
| case MEMMODEL_SEQ_CST: |
| return "dbar\t0x12\\n\\t" |
| "vst\t%w1,%0\\n\\t" |
| "dbar\t0x18"; |
| case MEMMODEL_RELEASE: |
| return "dbar\t0x12\\n\\t" |
| "vst\t%w1,%0"; |
| case MEMMODEL_RELAXED: |
| return "vst\t%w1,%0"; |
| default: |
| gcc_unreachable (); |
| } |
| } |
| [(set (attr "length") (const_int 12))]) |
| |
| (define_expand "atomic_storeti" |
| [(match_operand:TI 0 "memory_operand" "=m") |
| (match_operand:TI 1 "reg_or_0_operand" "rJ") |
| (match_operand:SI 2 "const_int_operand")] |
| "loongarch_16b_atomic_lock_free_p ()" |
| { |
| rtx vr = gen_reg_rtx (V2DImode), op1 = operands[1]; |
| rtvec v = rtvec_alloc (2); |
| |
| for (int i = 0; i < 2; i++) |
| RTVEC_ELT (v, i) = loongarch_subword (op1, i); |
| |
| emit_insn (gen_vec_initv2didi (vr, gen_rtx_PARALLEL (V2DImode, v))); |
| emit_insn (gen_atomic_storeti_lsx (operands[0], vr, operands[2])); |
| DONE; |
| }) |
| |
| (define_expand "atomic_<amop><mode>" |
| [(any_atomic:GPR (match_operand:GPR 0 "memory_operand") ;; mem location |
| (match_operand:GPR 1 "reg_or_0_operand")) ;; value for op |
| (match_operand:SI 2 "const_int_operand")] ;; model |
| "" |
| { |
| if (TARGET_64BIT) |
| emit_insn (gen_la64_atomic_<amop><mode> (operands[0], operands[1], operands[2])); |
| else |
| emit_insn (gen_la32_atomic_<amop>si (operands[0], operands[1], operands[2])); |
| DONE; |
| }) |
| |
| (define_insn "la64_atomic_<amop><mode>" |
| [(set (match_operand:GPR 0 "memory_operand" "+ZB") |
| (unspec_volatile:GPR |
| [(any_atomic:GPR (match_dup 0) |
| (match_operand:GPR 1 "reg_or_0_operand" "rJ")) |
| (match_operand:SI 2 "const_int_operand")] ;; model |
| UNSPEC_SYNC_OLD_OP))] |
| "TARGET_64BIT" |
| "am<amop>%A2.<size>\t$zero,%z1,%0" |
| [(set (attr "length") (const_int 4))]) |
| |
| (define_insn "la32_atomic_<amop>si" |
| [(set (match_operand:SI 0 "memory_operand" "+ZB") |
| (unspec_volatile:SI |
| [(any_atomic:SI (match_dup 0) |
| (match_operand:SI 1 "reg_or_0_operand" "rJ")) |
| (match_operand:SI 2 "const_int_operand")] ;; model |
| UNSPEC_SYNC_OLD_OP)) |
| (clobber (match_scratch:SI 3 "=&r"))] |
| "!TARGET_64BIT" |
| { |
| return "1:\n\t" |
| "ll.w\t%3,%0\n\t" |
| "<atomic_optab_insn>\t%3,%z1,%3\n\t" |
| "sc.w\t%3,%0\n\t" |
| "beq\t$zero,%3,1b\n\t"; |
| } |
| [(set (attr "length") (const_int 16))]) |
| |
| (define_insn "atomic_add<mode>" |
| [(set (match_operand:SHORT 0 "memory_operand" "+ZB") |
| (unspec_volatile:SHORT |
| [(plus:SHORT (match_dup 0) |
| (match_operand:SHORT 1 "reg_or_0_operand" "rJ")) |
| (match_operand:SI 2 "const_int_operand")] ;; model |
| UNSPEC_SYNC_OLD_OP))] |
| "ISA_HAS_LAM_BH" |
| "amadd%A2.<size>\t$zero,%z1,%0" |
| [(set (attr "length") (const_int 4))]) |
| |
| (define_expand "atomic_fetch_<amop><mode>" |
| [(match_operand:GPR 0 "register_operand") ;; old value at mem |
| (any_atomic:GPR (match_operand:GPR 1 "memory_operand") ;; mem location |
| (match_operand:GPR 2 "reg_or_0_operand")) ;; value for op |
| (match_operand:SI 3 "const_int_operand")] ;; model |
| "" |
| { |
| if (TARGET_64BIT) |
| emit_insn (gen_la64_atomic_fetch_<amop><mode> (operands[0], operands[1], |
| operands[2], operands[3])); |
| else |
| emit_insn (gen_la32_atomic_fetch_<amop>si (operands[0], operands[1], |
| operands[2], operands[3])); |
| DONE; |
| }) |
| |
| (define_insn "la64_atomic_fetch_<amop><mode>" |
| [(set (match_operand:GPR 0 "register_operand" "=&r") |
| (match_operand:GPR 1 "memory_operand" "+ZB")) |
| (set (match_dup 1) |
| (unspec_volatile:GPR |
| [(any_atomic:GPR (match_dup 1) |
| (match_operand:GPR 2 "reg_or_0_operand" "rJ")) |
| (match_operand:SI 3 "const_int_operand")] ;; model |
| UNSPEC_SYNC_OLD_OP))] |
| "TARGET_64BIT" |
| "am<amop>%A3.<size>\t%0,%z2,%1" |
| [(set (attr "length") (const_int 4))]) |
| |
| (define_insn "la32_atomic_fetch_<amop>si" |
| [(set (match_operand:SI 0 "register_operand" "=&r") |
| (match_operand:SI 1 "memory_operand" "+ZB")) |
| (set (match_dup 1) |
| (unspec_volatile:SI |
| [(any_atomic:SI (match_dup 1) |
| (match_operand:SI 2 "reg_or_0_operand" "rJ")) |
| (match_operand:SI 3 "const_int_operand")] ;; model |
| UNSPEC_SYNC_OLD_OP)) |
| (clobber (match_scratch:SI 4 "=&r"))] |
| "!TARGET_64BIT" |
| { |
| return "1:\n\t" |
| "ll.w\t%0,%1\n\t" |
| "<atomic_optab_insn>\t%4,%z2,%0\n\t" |
| "sc.w\t%4,%1\n\t" |
| "beq\t$zero,%4,1b\n\t"; |
| } |
| [(set (attr "length") (const_int 16))]) |
| |
| (define_insn "atomic_fetch_nand_mask_inverted<mode>" |
| [(set (match_operand:GPR 0 "register_operand" "=&r") |
| (match_operand:GPR 1 "memory_operand" "+ZC")) |
| (set (match_dup 1) |
| (unspec_volatile:GPR |
| [(ior:GPR (not (match_dup 1)) |
| (match_operand:GPR 2 "register_operand" "r"))] |
| UNSPEC_SYNC_OLD_OP)) |
| (clobber (match_scratch:GPR 3 "=&r"))] |
| "" |
| { |
| output_asm_insn ("1:", operands); |
| output_asm_insn ("ll.<d>\t%0,%1", operands); |
| if (TARGET_32BIT_R) |
| { |
| output_asm_insn ("nor\t%3,%0,$zero", operands); |
| output_asm_insn ("or\t%3,%2,%3", operands); |
| } |
| else |
| output_asm_insn ("orn\t%3,%2,%0", operands); |
| output_asm_insn ("sc.<d>\t%3,%1", operands); |
| output_asm_insn ("beq\t%3,$zero,1b", operands); |
| return ""; |
| } |
| [(set (attr "length") (if_then_else |
| (match_test "TARGET_32BIT_R") |
| (const_int 20) |
| (const_int 16)))]) |
| |
| (define_mode_iterator ALL_SC [GPR (TI "loongarch_16b_atomic_lock_free_p ()")]) |
| (define_mode_attr _scq [(SI "") (DI "") (TI "_scq")]) |
| (define_expand "atomic_fetch_nand<mode>" |
| [(match_operand:ALL_SC 0 "register_operand") |
| (match_operand:ALL_SC 1 "memory_operand") |
| (match_operand:ALL_SC 2 "reg_or_0_operand") |
| (match_operand:SI 3 "const_int_operand")] |
| "" |
| { |
| /* ~(atom & mask) = (~mask) | (~atom), so we can hoist |
| (~mask) out of the ll/sc loop and use the orn instruction in the |
| ll/sc loop. */ |
| rtx inverted_mask = gen_reg_rtx (<MODE>mode); |
| emit_move_insn (inverted_mask, |
| expand_simple_unop (<MODE>mode, NOT, operands[2], |
| NULL_RTX, false)); |
| |
| emit_insn ( |
| gen_atomic_fetch_nand_mask_inverted<mode><_scq> (operands[0], |
| operands[1], |
| inverted_mask)); |
| DONE; |
| }) |
| |
| (define_expand "atomic_exchange<mode>" |
| [(match_operand:GPR 0 "register_operand") ;; old value at mem |
| (match_operand:GPR 1 "memory_operand") ;; mem location |
| (match_operand:GPR 2 "register_operand") ;; value for op |
| (match_operand:SI 3 "const_int_operand")] ;; model |
| "" |
| { |
| if (TARGET_64BIT) |
| emit_insn (gen_la64_atomic_exchange<mode> (operands[0], operands[1], |
| operands[2], operands[3])); |
| else |
| emit_insn (gen_la32_atomic_exchangesi (operands[0], operands[1], |
| operands[2], operands[3])); |
| DONE; |
| }) |
| |
| (define_insn "la64_atomic_exchange<mode>" |
| [(set (match_operand:GPR 0 "register_operand" "=&r") |
| (unspec_volatile:GPR |
| [(match_operand:GPR 1 "memory_operand" "+ZB") |
| (match_operand:SI 3 "const_int_operand")] ;; model |
| UNSPEC_SYNC_EXCHANGE)) |
| (set (match_dup 1) |
| (match_operand:GPR 2 "register_operand" "r"))] |
| "TARGET_64BIT" |
| "amswap%A3.<size>\t%0,%z2,%1" |
| [(set (attr "length") (const_int 4))]) |
| |
| (define_insn "la32_atomic_exchangesi" |
| [(set (match_operand:SI 0 "register_operand" "=&r") |
| (unspec_volatile:SI |
| [(match_operand:SI 1 "memory_operand" "+ZB") |
| (match_operand:SI 3 "const_int_operand")] ;; model |
| UNSPEC_SYNC_EXCHANGE)) |
| (set (match_dup 1) |
| (match_operand:SI 2 "register_operand" "r")) |
| (clobber (match_scratch:SI 4 "=&r"))] |
| "!TARGET_64BIT" |
| { |
| return "1:\n\t" |
| "ll.w\t%0,%1\n\t" |
| "or\t%4,$zero,%2\n\t" |
| "sc.w\t%4,%1\n\t" |
| "beq\t$zero,%4,1b\n\t"; |
| } |
| [(set (attr "length") (const_int 16))]) |
| |
| (define_insn "atomic_exchangeti_scq" |
| [(set (match_operand:TI 0 "register_operand" "=&r") |
| (unspec_volatile:TI |
| [(match_operand:TI 1 "memory_operand" "+ZB")] |
| UNSPEC_SYNC_EXCHANGE)) |
| (set (match_dup 1) |
| (match_operand:TI 2 "register_operand" "rJ")) |
| (clobber (match_scratch:DI 3 "=&r"))] |
| "loongarch_16b_atomic_lock_free_p ()" |
| { |
| output_asm_insn ("1:", operands); |
| output_asm_insn ("ll.d\t%0,%1", operands); |
| if (!ISA_HAS_LD_SEQ_SA) |
| output_asm_insn ("dbar\t0x700", operands); |
| output_asm_insn ("ld.d\t%t0,%b1,8", operands); |
| output_asm_insn ("move\t%3,%z2", operands); |
| output_asm_insn ("sc.q\t%3,%t2,%1", operands); |
| output_asm_insn ("beq\t%3,$zero,1b", operands); |
| |
| return ""; |
| } |
| [(set (attr "length") (const_int 24))]) |
| |
| (define_expand "atomic_exchangeti" |
| [(match_operand:TI 0 "register_operand" "=&r") |
| (match_operand:TI 1 "memory_operand" "+ZB") |
| (match_operand:TI 2 "register_operand" "rJ") |
| (match_operand:SI 3 "const_int_operand")] ;; model |
| "loongarch_16b_atomic_lock_free_p ()" |
| { |
| emit_insn (gen_atomic_exchangeti_scq (operands[0], operands[1], |
| operands[2])); |
| DONE; |
| }) |
| |
| (define_insn "atomic_exchange<mode>_short" |
| [(set (match_operand:SHORT 0 "register_operand" "=&r") |
| (unspec_volatile:SHORT |
| [(match_operand:SHORT 1 "memory_operand" "+ZB") |
| (match_operand:SI 3 "const_int_operand")] ;; model |
| UNSPEC_SYNC_EXCHANGE)) |
| (set (match_dup 1) |
| (match_operand:SHORT 2 "register_operand" "r"))] |
| "ISA_HAS_LAM_BH" |
| "amswap%A3.<size>\t%0,%z2,%1" |
| [(set (attr "length") (const_int 4))]) |
| |
| (define_insn "atomic_cas_value_strong<mode>" |
| [(set (match_operand:GPR 0 "register_operand" "=&r") |
| (match_operand:GPR 1 "memory_operand" "+ZC")) |
| (set (match_dup 1) |
| (unspec_volatile:GPR [(match_operand:GPR 2 "reg_or_0_operand" "rJ") |
| (match_operand:GPR 3 "reg_or_0_operand" "rJ") |
| (match_operand:SI 4 "const_int_operand")] ;; mod_f |
| UNSPEC_COMPARE_AND_SWAP)) |
| (clobber (match_scratch:GPR 5 "=&r"))] |
| "" |
| { |
| output_asm_insn ("1:", operands); |
| output_asm_insn ("ll.<size>\t%0,%1", operands); |
| |
| /* Like the test case atomic-cas-int.C, in loongarch64, O1 and higher, the |
| return value of the val_without_const_folding will not be truncated and |
| will be passed directly to the function compare_exchange_strong. |
| However, the instruction 'bne' does not distinguish between 32-bit and |
| 64-bit operations. so if the upper 32 bits of the register are not |
| extended by the 32nd bit symbol, then the comparison may not be valid |
| here. This will affect the result of the operation. */ |
| |
| if (TARGET_64BIT && REG_P (operands[2]) |
| && GET_MODE (operands[2]) == SImode) |
| { |
| output_asm_insn ("addi.w\t%5,%2,0", operands); |
| output_asm_insn ("bne\t%0,%5,2f", operands); |
| } |
| else |
| output_asm_insn ("bne\t%0,%z2,2f", operands); |
| |
| output_asm_insn ("or%i3\t%5,$zero,%3", operands); |
| output_asm_insn ("sc.<size>\t%5,%1", operands); |
| output_asm_insn ("beq\t%5,$zero,1b", operands); |
| output_asm_insn ("%T4b\t3f", operands); |
| output_asm_insn ("2:", operands); |
| output_asm_insn ("%G4", operands); |
| output_asm_insn ("3:", operands); |
| |
| return ""; |
| } |
| [(set (attr "length") |
| (if_then_else |
| (and (match_test "GET_MODE (operands[2]) == SImode") |
| (match_test "REG_P (operands[2])")) |
| (const_int 32) |
| (const_int 28)))]) |
| |
| (define_insn "atomic_cas_value_strong<mode>_amcas" |
| [(set (match_operand:QHWD 0 "register_operand" "=&r") |
| (match_operand:QHWD 1 "memory_operand" "+ZB")) |
| (set (match_dup 1) |
| (unspec_volatile:QHWD [(match_operand:QHWD 2 "reg_or_0_operand" "rJ") |
| (match_operand:QHWD 3 "reg_or_0_operand" "rJ") |
| (match_operand:SI 4 "const_int_operand")] ;; mod |
| UNSPEC_COMPARE_AND_SWAP_AMCAS))] |
| "ISA_HAS_LAMCAS" |
| "ori\t%0,%z2,0\n\tamcas%A4.<size>\t%0,%z3,%1" |
| [(set (attr "length") (const_int 8))]) |
| |
| (define_expand "atomic_compare_and_swap<mode>" |
| [(match_operand:SI 0 "register_operand" "") ;; bool output |
| (match_operand:GPR 1 "register_operand" "") ;; val output |
| (match_operand:GPR 2 "memory_operand" "") ;; memory |
| (match_operand:GPR 3 "reg_or_0_operand" "") ;; expected value |
| (match_operand:GPR 4 "reg_or_0_operand" "") ;; desired value |
| (match_operand:SI 5 "const_int_operand" "") ;; is_weak |
| (match_operand:SI 6 "const_int_operand" "") ;; mod_s |
| (match_operand:SI 7 "const_int_operand" "")] ;; mod_f |
| "" |
| { |
| rtx mod_s, mod_f; |
| |
| mod_s = operands[6]; |
| mod_f = operands[7]; |
| |
| /* Normally the succ memory model must be stronger than fail, but in the |
| unlikely event of fail being ACQUIRE and succ being RELEASE we need to |
| promote succ to ACQ_REL so that we don't lose the acquire semantics. */ |
| |
| if (is_mm_acquire (memmodel_base (INTVAL (mod_f))) |
| && is_mm_release (memmodel_base (INTVAL (mod_s)))) |
| mod_s = GEN_INT (MEMMODEL_ACQ_REL); |
| |
| if (ISA_HAS_LAMCAS) |
| emit_insn (gen_atomic_cas_value_strong<mode>_amcas (operands[1], operands[2], |
| operands[3], operands[4], |
| mod_s)); |
| else |
| emit_insn (gen_atomic_cas_value_strong<mode> (operands[1], operands[2], |
| operands[3], operands[4], |
| mod_f)); |
| |
| rtx compare = operands[1]; |
| if (operands[3] != const0_rtx) |
| { |
| rtx difference = gen_rtx_MINUS (<MODE>mode, operands[1], operands[3]); |
| compare = gen_reg_rtx (<MODE>mode); |
| emit_insn (gen_rtx_SET (compare, difference)); |
| } |
| |
| if (word_mode != GET_MODE (compare)) |
| { |
| rtx reg = gen_reg_rtx (word_mode); |
| emit_insn (gen_rtx_SET (reg, gen_rtx_SIGN_EXTEND (word_mode, compare))); |
| compare = reg; |
| } |
| |
| emit_insn (gen_rtx_SET (operands[0], |
| gen_rtx_EQ (SImode, compare, const0_rtx))); |
| DONE; |
| }) |
| |
| (define_expand "atomic_fetch_<amop><mode>" |
| [(match_operand:SHORT 0 "register_operand" "") ;; output |
| (any_bitwise (match_operand:SHORT 1 "memory_operand" "+ZB") ;; memory |
| (match_operand:SHORT 2 "reg_or_0_operand" "rJ")) ;; val |
| (match_operand:SI 3 "const_int_operand" "")] ;; model |
| "TARGET_64BIT || TARGET_32BIT_S" |
| { |
| /* We have no QI/HImode bitwise atomics, so use the address LSBs to form |
| a mask, then use an aligned SImode atomic. */ |
| rtx result = operands[0]; |
| rtx mem = operands[1]; |
| rtx model = operands[3]; |
| rtx addr = force_reg (Pmode, XEXP (mem, 0)); |
| rtx mask = gen_int_mode (-4, Pmode); |
| rtx aligned_addr = gen_reg_rtx (Pmode); |
| |
| if (!and_operand (mask, Pmode)) |
| mask = force_reg (Pmode, mask); |
| |
| emit_move_insn (aligned_addr, gen_rtx_AND (Pmode, addr, mask)); |
| |
| rtx aligned_mem = change_address (mem, SImode, aligned_addr); |
| set_mem_alias_set (aligned_mem, 0); |
| |
| rtx tmp = gen_reg_rtx (SImode); |
| emit_move_insn (tmp, simplify_gen_unary (ZERO_EXTEND, SImode, |
| operands[2], <MODE>mode)); |
| |
| /* Note that we have defined SHIFT_COUNT_TRUNCATED to 1, so we don't need |
| to mask addr with 0b11 here. */ |
| rtx shmt = gen_reg_rtx (SImode); |
| emit_move_insn (shmt, gen_rtx_ASHIFT (SImode, gen_lowpart (SImode, addr), |
| GEN_INT (3))); |
| |
| rtx word = gen_reg_rtx (SImode); |
| emit_move_insn (word, gen_rtx_ASHIFT (SImode, tmp, shmt)); |
| |
| if (<is_and>) |
| { |
| /* word = word | ~(mode_mask << shmt) */ |
| rtx tmp = force_reg (SImode, |
| gen_int_mode (GET_MODE_MASK (<MODE>mode), |
| SImode)); |
| emit_move_insn (tmp, gen_rtx_ASHIFT (SImode, tmp, shmt)); |
| emit_move_insn (word, gen_rtx_IOR (SImode, gen_rtx_NOT (SImode, tmp), |
| word)); |
| } |
| |
| tmp = gen_reg_rtx (SImode); |
| emit_insn (gen_atomic_fetch_<amop>si (tmp, aligned_mem, word, model)); |
| |
| emit_move_insn (gen_lowpart (SImode, result), |
| gen_rtx_LSHIFTRT (SImode, tmp, shmt)); |
| DONE; |
| }) |
| |
| (define_expand "atomic_test_and_set" |
| [(match_operand:QI 0 "register_operand" "") ;; bool output |
| (match_operand:QI 1 "memory_operand" "+ZB") ;; memory |
| (match_operand:SI 2 "const_int_operand" "")] ;; model |
| "" |
| { |
| rtx one = force_reg (QImode, gen_int_mode (1, QImode)); |
| emit_insn (gen_atomic_fetch_orqi (operands[0], operands[1], one, |
| operands[2])); |
| DONE; |
| }) |
| |
| (define_insn "atomic_cas_value_cmp_and_7_<mode>" |
| [(set (match_operand:GPR 0 "register_operand" "=&r") |
| (match_operand:GPR 1 "memory_operand" "+ZC")) |
| (set (match_dup 1) |
| (unspec_volatile:GPR [(match_operand:GPR 2 "reg_or_0_operand" "rJ") |
| (match_operand:GPR 3 "reg_or_0_operand" "rJ") |
| (match_operand:GPR 4 "reg_or_0_operand" "rJ") |
| (match_operand:GPR 5 "reg_or_0_operand" "rJ") |
| (match_operand:SI 6 "const_int_operand")] ;; mod_f |
| UNSPEC_COMPARE_AND_SWAP)) |
| (clobber (match_scratch:GPR 7 "=&r"))] |
| "" |
| { |
| return "1:\\n\\t" |
| "ll.<size>\\t%0,%1\\n\\t" |
| "and\\t%7,%0,%2\\n\\t" |
| "bne\\t%7,%z4,2f\\n\\t" |
| "and\\t%7,%0,%z3\\n\\t" |
| "or%i5\\t%7,%7,%5\\n\\t" |
| "sc.<size>\\t%7,%1\\n\\t" |
| "beq\\t$zero,%7,1b\\n\\t" |
| "%T6b\\t3f\\n\\t" |
| "2:\\n\\t" |
| "%G6\\n\\t" |
| "3:\\n\\t"; |
| } |
| [(set (attr "length") (const_int 36))]) |
| |
| (define_expand "atomic_compare_and_swap<mode>" |
| [(match_operand:SI 0 "register_operand" "") ;; bool output |
| (match_operand:SHORT 1 "register_operand" "") ;; val output |
| (match_operand:SHORT 2 "memory_operand" "") ;; memory |
| (match_operand:SHORT 3 "reg_or_0_operand" "") ;; expected value |
| (match_operand:SHORT 4 "reg_or_0_operand" "") ;; desired value |
| (match_operand:SI 5 "const_int_operand" "") ;; is_weak |
| (match_operand:SI 6 "const_int_operand" "") ;; mod_s |
| (match_operand:SI 7 "const_int_operand" "")] ;; mod_f |
| "" |
| { |
| rtx mod_s, mod_f; |
| |
| mod_s = operands[6]; |
| mod_f = operands[7]; |
| |
| /* Normally the succ memory model must be stronger than fail, but in the |
| unlikely event of fail being ACQUIRE and succ being RELEASE we need to |
| promote succ to ACQ_REL so that we don't lose the acquire semantics. */ |
| |
| if (is_mm_acquire (memmodel_base (INTVAL (mod_f))) |
| && is_mm_release (memmodel_base (INTVAL (mod_s)))) |
| mod_s = GEN_INT (MEMMODEL_ACQ_REL); |
| |
| if (ISA_HAS_LAMCAS) |
| emit_insn (gen_atomic_cas_value_strong<mode>_amcas (operands[1], operands[2], |
| operands[3], operands[4], |
| mod_s)); |
| else |
| { |
| union loongarch_gen_fn_ptrs generator; |
| generator.fn_7 = gen_atomic_cas_value_cmp_and_7_si; |
| loongarch_expand_atomic_qihi (generator, operands[1], operands[2], |
| operands[3], operands[4], mod_f); |
| } |
| |
| rtx compare = operands[1]; |
| if (operands[3] != const0_rtx) |
| { |
| machine_mode mode = GET_MODE (operands[3]); |
| rtx op1 = convert_modes (SImode, mode, operands[1], true); |
| rtx op3 = convert_modes (SImode, mode, operands[3], true); |
| rtx difference = gen_rtx_MINUS (SImode, op1, op3); |
| compare = gen_reg_rtx (SImode); |
| emit_insn (gen_rtx_SET (compare, difference)); |
| } |
| |
| if (word_mode != GET_MODE (compare)) |
| { |
| rtx reg = gen_reg_rtx (word_mode); |
| emit_insn (gen_rtx_SET (reg, gen_rtx_SIGN_EXTEND (word_mode, compare))); |
| compare = reg; |
| } |
| |
| emit_insn (gen_rtx_SET (operands[0], |
| gen_rtx_EQ (SImode, compare, const0_rtx))); |
| DONE; |
| }) |
| |
| (define_insn "atomic_compare_and_swapti_scq" |
| [(set (match_operand:V2DI 0 "register_operand" "=&f") |
| (match_operand:V2DI 1 "memory_operand" "+m")) |
| (set (match_dup 1) |
| (unspec_volatile:V2DI |
| [(match_operand:V2DI 2 "reg_or_0_operand" "fJ") |
| (match_operand:TI 3 "reg_or_0_operand" "rJ") |
| (match_operand:SI 4 "const_int_operand")] |
| UNSPEC_COMPARE_AND_SWAP)) |
| (set (match_operand:FCC 5 "register_operand" "=z") |
| (ne:FCC (match_dup 1) (match_dup 2))) |
| (clobber (match_scratch:V2DI 6 "=&f")) |
| (clobber (match_scratch:DI 7 "=&r"))] |
| "loongarch_16b_atomic_lock_free_p ()" |
| { |
| output_asm_insn ("1:", operands); |
| |
| /* The loaded value in %7 will just be discarded, this instruction is |
| only intended to raise the LL bit. Should we use %. instead?? */ |
| output_asm_insn ("ll.d\t%7,%1", operands); |
| |
| /* Don't reorder the load of high word before ll.d. As the TImode |
| must be aligned in the memory, the high and low words must be in |
| the same cacheline, thus dbar 0x700 is enough. */ |
| if (!ISA_HAS_LD_SEQ_SA) |
| output_asm_insn ("dbar\t0x700", operands); |
| |
| /* Load the word pair altogether. We cannot just load the high word |
| alone: doing so will need to rely on sc.q to ensure no other threads |
| have updated the memory between two loads, but issuing an sc.q when |
| original != expected will cause a page fault with a "valid" (well, at |
| least the standard implies it's valid) use of |
| atomic_compare_and_exchange on a const _Atomic object. See |
| https://gcc.gnu.org/PR80878#c1. */ |
| output_asm_insn ("vld\t%w0,%1", operands); |
| |
| /* Compare the word pair. */ |
| if (const_0_operand (operands[2], V1TImode)) |
| operands[7] = operands[0]; |
| else |
| output_asm_insn ("vxor.v\t%w6,%w0,%w2", operands); |
| output_asm_insn ("vseteqz.v\t%5,%w6", operands); |
| output_asm_insn ("bceqz\t%5,2f", operands); |
| |
| /* Copy the low word of the new value as it'll be clobbered by sc.q. */ |
| output_asm_insn ("move\t%7,%z3", operands); |
| |
| /* Store both words if LL bit is still set. */ |
| output_asm_insn ("sc.q\t%7,%t3,%1", operands); |
| |
| /* Check if sc.q has done the store. */ |
| output_asm_insn ("beq\t%7,$zero,1b", operands); |
| |
| /* Jump over the mod_f barrier if sc.q has succeeded. */ |
| output_asm_insn ("%T4b\t3f", operands); |
| |
| /* The barrier for mod_f. */ |
| output_asm_insn ("2:", operands); |
| output_asm_insn ("%G4", operands); |
| |
| output_asm_insn ("3:", operands); |
| return ""; |
| } |
| [(set_attr "length" "44")]) |
| |
| (define_expand "atomic_compare_and_swapti" |
| [(match_operand:SI 0 "register_operand" "") ;; bool output |
| (match_operand:TI 1 "register_operand" "") ;; val output |
| (match_operand:TI 2 "memory_operand" "") ;; memory |
| (match_operand:TI 3 "reg_or_0_operand" "") ;; expected value |
| (match_operand:TI 4 "reg_or_0_operand" "") ;; desired value |
| (match_operand:SI 5 "const_int_operand" "") ;; is_weak |
| (match_operand:SI 6 "const_int_operand" "") ;; mod_s |
| (match_operand:SI 7 "const_int_operand" "")] ;; mod_f |
| "loongarch_16b_atomic_lock_free_p ()" |
| { |
| rtx fcc = gen_reg_rtx (FCCmode); |
| rtx gpr = gen_reg_rtx (DImode); |
| rtx vr = gen_reg_rtx (V2DImode); |
| rtx mem = gen_rtx_MEM (V2DImode, XEXP (operands[2], 0)); |
| |
| if (const_0_operand (operands[3], TImode)) |
| operands[3] = CONST0_RTX (V2DImode); |
| else |
| { |
| rtvec v = rtvec_alloc (2); |
| for (int i = 0; i < 2; i++) |
| RTVEC_ELT (v, i) = loongarch_subword (operands[3], i); |
| |
| operands[3] = gen_reg_rtx (V2DImode); |
| emit_insn (gen_vec_initv2didi (operands[3], |
| gen_rtx_PARALLEL (V2DImode, v))); |
| } |
| |
| emit_insn (gen_atomic_compare_and_swapti_scq (vr, mem, operands[3], |
| operands[4], operands[7], |
| fcc)); |
| |
| for (int i = 0; i < 2; i++) |
| emit_insn ( |
| gen_lsx_vpickve2gr_d (loongarch_subword (operands[1], i), vr, |
| GEN_INT (i))); |
| |
| emit_insn (gen_fcc_to_di (gpr, fcc)); |
| gpr = gen_lowpart (SImode, gpr); |
| SUBREG_PROMOTED_VAR_P (gpr) = 1; |
| SUBREG_PROMOTED_SET (gpr, SRP_SIGNED); |
| emit_move_insn (operands[0], gpr); |
| DONE; |
| }) |
| |
| (define_insn "atomic_cas_value_add_7_<mode>" |
| [(set (match_operand:GPR 0 "register_operand" "=&r") ;; res |
| (match_operand:GPR 1 "memory_operand" "+ZC")) |
| (set (match_dup 1) |
| (unspec_volatile:GPR [(match_operand:GPR 2 "reg_or_0_operand" "rJ") ;; mask |
| (match_operand:GPR 3 "reg_or_0_operand" "rJ") ;; inverted_mask |
| (match_operand:GPR 4 "reg_or_0_operand" "rJ") ;; old val |
| (match_operand:GPR 5 "reg_or_0_operand" "rJ") ;; new val |
| (match_operand:SI 6 "const_int_operand")] ;; model |
| UNSPEC_COMPARE_AND_SWAP_ADD)) |
| (clobber (match_scratch:GPR 7 "=&r")) |
| (clobber (match_scratch:GPR 8 "=&r"))] |
| "" |
| { |
| return "1:\\n\\t" |
| "ll.<size>\\t%0,%1\\n\\t" |
| "and\\t%7,%0,%3\\n\\t" |
| "add.w\\t%8,%0,%z5\\n\\t" |
| "and\\t%8,%8,%z2\\n\\t" |
| "or%i8\\t%7,%7,%8\\n\\t" |
| "sc.<size>\\t%7,%1\\n\\t" |
| "beq\\t$zero,%7,1b"; |
| } |
| |
| [(set (attr "length") (const_int 28))]) |
| |
| (define_insn "atomic_cas_value_sub_7_<mode>" |
| [(set (match_operand:GPR 0 "register_operand" "=&r") ;; res |
| (match_operand:GPR 1 "memory_operand" "+ZC")) |
| (set (match_dup 1) |
| (unspec_volatile:GPR [(match_operand:GPR 2 "reg_or_0_operand" "rJ") ;; mask |
| (match_operand:GPR 3 "reg_or_0_operand" "rJ") ;; inverted_mask |
| (match_operand:GPR 4 "reg_or_0_operand" "rJ") ;; old val |
| (match_operand:GPR 5 "reg_or_0_operand" "rJ") ;; new val |
| (match_operand:SI 6 "const_int_operand")] ;; model |
| UNSPEC_COMPARE_AND_SWAP_SUB)) |
| (clobber (match_scratch:GPR 7 "=&r")) |
| (clobber (match_scratch:GPR 8 "=&r"))] |
| "" |
| { |
| return "1:\\n\\t" |
| "ll.<size>\\t%0,%1\\n\\t" |
| "and\\t%7,%0,%3\\n\\t" |
| "sub.w\\t%8,%0,%z5\\n\\t" |
| "and\\t%8,%8,%z2\\n\\t" |
| "or%i8\\t%7,%7,%8\\n\\t" |
| "sc.<size>\\t%7,%1\\n\\t" |
| "beq\\t$zero,%7,1b"; |
| } |
| [(set (attr "length") (const_int 28))]) |
| |
| (define_insn "atomic_cas_value_nand_7_<mode>" |
| [(set (match_operand:GPR 0 "register_operand" "=&r") ;; res |
| (match_operand:GPR 1 "memory_operand" "+ZC")) |
| (set (match_dup 1) |
| (unspec_volatile:GPR [(match_operand:GPR 2 "reg_or_0_operand" "rJ") ;; mask |
| (match_operand:GPR 3 "reg_or_0_operand" "rJ") ;; inverted_mask |
| (match_operand:GPR 4 "reg_or_0_operand" "rJ") ;; old val |
| (match_operand:GPR 5 "reg_or_0_operand" "rJ") ;; new val |
| (match_operand:SI 6 "const_int_operand")] ;; model |
| UNSPEC_COMPARE_AND_SWAP_NAND)) |
| (clobber (match_scratch:GPR 7 "=&r")) |
| (clobber (match_scratch:GPR 8 "=&r"))] |
| "" |
| { |
| return "1:\\n\\t" |
| "ll.<size>\\t%0,%1\\n\\t" |
| "and\\t%7,%0,%3\\n\\t" |
| "and\\t%8,%0,%z5\\n\\t" |
| "xor\\t%8,%8,%z2\\n\\t" |
| "or%i8\\t%7,%7,%8\\n\\t" |
| "sc.<size>\\t%7,%1\\n\\t" |
| "beq\\t$zero,%7,1b"; |
| } |
| [(set (attr "length") (const_int 28))]) |
| |
| (define_insn "atomic_cas_value_exchange_7_<mode>" |
| [(set (match_operand:GPR 0 "register_operand" "=&r") |
| (match_operand:GPR 1 "memory_operand" "+ZC")) |
| (set (match_dup 1) |
| (unspec_volatile:GPR [(match_operand:GPR 2 "reg_or_0_operand" "rJ") |
| (match_operand:GPR 3 "reg_or_0_operand" "rJ") |
| (match_operand:GPR 4 "reg_or_0_operand" "rJ") |
| (match_operand:GPR 5 "reg_or_0_operand" "rJ") |
| (match_operand:SI 6 "const_int_operand")] ;; model |
| UNSPEC_SYNC_EXCHANGE)) |
| (clobber (match_scratch:GPR 7 "=&r"))] |
| "" |
| { |
| return "1:\\n\\t" |
| "ll.<size>\\t%0,%1\\n\\t" |
| "and\\t%7,%0,%z3\\n\\t" |
| "or%i5\\t%7,%7,%5\\n\\t" |
| "sc.<size>\\t%7,%1\\n\\t" |
| "beq\\t%7,$zero,1b\\n\\t"; |
| } |
| [(set (attr "length") (const_int 20))]) |
| |
| (define_expand "atomic_exchange<mode>" |
| [(set (match_operand:SHORT 0 "register_operand") |
| (unspec_volatile:SHORT |
| [(match_operand:SHORT 1 "memory_operand") |
| (match_operand:SI 3 "const_int_operand")] ;; model |
| UNSPEC_SYNC_EXCHANGE)) |
| (set (match_dup 1) |
| (match_operand:SHORT 2 "register_operand"))] |
| "" |
| { |
| if (ISA_HAS_LAM_BH) |
| emit_insn (gen_atomic_exchange<mode>_short (operands[0], operands[1], operands[2], operands[3])); |
| else |
| { |
| union loongarch_gen_fn_ptrs generator; |
| generator.fn_7 = gen_atomic_cas_value_exchange_7_si; |
| loongarch_expand_atomic_qihi (generator, operands[0], operands[1], |
| const0_rtx, operands[2], operands[3]); |
| } |
| DONE; |
| }) |
| |
| (define_int_iterator UNSPEC_TI_FETCH_DIRECT |
| [UNSPEC_TI_FETCH_ADD |
| UNSPEC_TI_FETCH_SUB |
| UNSPEC_TI_FETCH_AND |
| UNSPEC_TI_FETCH_XOR |
| UNSPEC_TI_FETCH_OR]) |
| (define_int_iterator UNSPEC_TI_FETCH |
| [UNSPEC_TI_FETCH_DIRECT UNSPEC_TI_FETCH_NAND_MASK_INVERTED]) |
| (define_int_attr amop_ti_fetch |
| [(UNSPEC_TI_FETCH_ADD "add") |
| (UNSPEC_TI_FETCH_SUB "sub") |
| (UNSPEC_TI_FETCH_AND "and") |
| (UNSPEC_TI_FETCH_XOR "xor") |
| (UNSPEC_TI_FETCH_OR "or") |
| (UNSPEC_TI_FETCH_NAND_MASK_INVERTED "nand_mask_inverted")]) |
| (define_int_attr size_ti_fetch |
| [(UNSPEC_TI_FETCH_ADD "36") |
| (UNSPEC_TI_FETCH_SUB "36") |
| (UNSPEC_TI_FETCH_AND "28") |
| (UNSPEC_TI_FETCH_XOR "28") |
| (UNSPEC_TI_FETCH_OR "28") |
| (UNSPEC_TI_FETCH_NAND_MASK_INVERTED "28")]) |
| |
| (define_insn "atomic_fetch_<amop_ti_fetch>ti_scq" |
| [(set (match_operand:TI 0 "register_operand" "=&r") |
| (match_operand:TI 1 "memory_operand" "+ZB")) |
| (set (match_dup 1) |
| (unspec_volatile:TI |
| [(match_dup 0) |
| (match_operand:TI 2 "reg_or_0_operand" "rJ")] |
| UNSPEC_TI_FETCH)) |
| (clobber (match_scratch:DI 3 "=&r")) |
| (clobber (match_scratch:DI 4 "=&r"))] |
| "loongarch_16b_atomic_lock_free_p ()" |
| { |
| output_asm_insn ("1:", operands); |
| output_asm_insn ("ll.d\t%0,%1", operands); |
| if (!ISA_HAS_LD_SEQ_SA) |
| output_asm_insn ("dbar\t0x700", operands); |
| output_asm_insn ("ld.d\t%t0,%b1,8", operands); |
| |
| switch (<UNSPEC_TI_FETCH>) |
| { |
| case UNSPEC_TI_FETCH_AND: |
| case UNSPEC_TI_FETCH_OR: |
| case UNSPEC_TI_FETCH_XOR: |
| output_asm_insn ("<amop_ti_fetch>\t%3,%0,%z2", operands); |
| output_asm_insn ("<amop_ti_fetch>\t%4,%t0,%t2", operands); |
| break; |
| case UNSPEC_TI_FETCH_NAND_MASK_INVERTED: |
| output_asm_insn ("orn\t%3,%z2,%0", operands); |
| output_asm_insn ("orn\t%4,%t2,%t0", operands); |
| break; |
| case UNSPEC_TI_FETCH_ADD: |
| case UNSPEC_TI_FETCH_SUB: |
| output_asm_insn ("<amop_ti_fetch>.d\t%3,%0,%z2", operands); |
| |
| /* Generate carry bit. */ |
| output_asm_insn ( |
| <UNSPEC_TI_FETCH> == UNSPEC_TI_FETCH_ADD ? "sltu\t%4,%3,%0" |
| : "sltu\t%4,%0,%3", |
| operands); |
| |
| output_asm_insn ("<amop_ti_fetch>.d\t%4,%t0,%4", operands); |
| output_asm_insn ("<amop_ti_fetch>.d\t%4,%4,%t2", operands); |
| break; |
| default: |
| gcc_unreachable (); |
| } |
| |
| output_asm_insn ("sc.q\t%3,%4,%1", operands); |
| output_asm_insn ("beq\t%3,$zero,1b", operands); |
| |
| return ""; |
| } |
| [(set_attr "length" "<size_ti_fetch>")]) |
| |
| (define_expand "atomic_fetch_<amop_ti_fetch>ti" |
| [(set (match_operand:TI 0 "register_operand" "=&r") |
| (match_operand:TI 1 "memory_operand" "+ZB")) |
| (set (match_dup 1) |
| (unspec_volatile:TI |
| [(match_dup 0) |
| (match_operand:TI 2 "reg_or_0_operand" "rJ")] |
| UNSPEC_TI_FETCH_DIRECT)) |
| (match_operand:SI 3 "const_int_operand")] ;; model |
| "loongarch_16b_atomic_lock_free_p ()" |
| { |
| /* Model is ignored as sc.q implies a full barrier. */ |
| emit_insn (gen_atomic_fetch_<amop_ti_fetch>ti_scq (operands[0], |
| operands[1], |
| operands[2])); |
| DONE; |
| }) |
| |
| (define_insn "atomic_fetch_add<mode>_short" |
| [(set (match_operand:SHORT 0 "register_operand" "=&r") |
| (match_operand:SHORT 1 "memory_operand" "+ZB")) |
| (set (match_dup 1) |
| (unspec_volatile:SHORT |
| [(plus:SHORT (match_dup 1) |
| (match_operand:SHORT 2 "reg_or_0_operand" "rJ")) |
| (match_operand:SI 3 "const_int_operand")] ;; model |
| UNSPEC_SYNC_OLD_OP))] |
| "ISA_HAS_LAM_BH" |
| "amadd%A3.<size>\t%0,%z2,%1" |
| [(set (attr "length") (const_int 4))]) |
| |
| (define_expand "atomic_fetch_add<mode>" |
| [(set (match_operand:SHORT 0 "register_operand" "=&r") |
| (match_operand:SHORT 1 "memory_operand" "+ZB")) |
| (set (match_dup 1) |
| (unspec_volatile:SHORT |
| [(plus:SHORT (match_dup 1) |
| (match_operand:SHORT 2 "reg_or_0_operand" "rJ")) |
| (match_operand:SI 3 "const_int_operand")] ;; model |
| UNSPEC_SYNC_OLD_OP))] |
| "" |
| { |
| if (ISA_HAS_LAM_BH) |
| emit_insn (gen_atomic_fetch_add<mode>_short (operands[0], operands[1], |
| operands[2], operands[3])); |
| else |
| { |
| union loongarch_gen_fn_ptrs generator; |
| generator.fn_7 = gen_atomic_cas_value_add_7_si; |
| loongarch_expand_atomic_qihi (generator, operands[0], operands[1], |
| operands[1], operands[2], operands[3]); |
| } |
| DONE; |
| }) |
| |
| (define_expand "atomic_fetch_sub<mode>" |
| [(set (match_operand:SHORT 0 "register_operand" "=&r") |
| (match_operand:SHORT 1 "memory_operand" "+ZB")) |
| (set (match_dup 1) |
| (unspec_volatile:SHORT |
| [(minus:SHORT (match_dup 1) |
| (match_operand:SHORT 2 "reg_or_0_operand" "rJ")) |
| (match_operand:SI 3 "const_int_operand")] ;; model |
| UNSPEC_SYNC_OLD_OP))] |
| "!ISA_HAS_LAM_BH" |
| { |
| union loongarch_gen_fn_ptrs generator; |
| generator.fn_7 = gen_atomic_cas_value_sub_7_si; |
| loongarch_expand_atomic_qihi (generator, operands[0], operands[1], |
| operands[1], operands[2], operands[3]); |
| DONE; |
| }) |
| |
| (define_expand "atomic_fetch_nand<mode>" |
| [(set (match_operand:SHORT 0 "register_operand" "=&r") |
| (match_operand:SHORT 1 "memory_operand" "+ZB")) |
| (set (match_dup 1) |
| (unspec_volatile:SHORT |
| [(not:SHORT (and:SHORT (match_dup 1) |
| (match_operand:SHORT 2 "reg_or_0_operand" "rJ"))) |
| (match_operand:SI 3 "const_int_operand")] ;; model |
| UNSPEC_SYNC_OLD_OP))] |
| "" |
| { |
| union loongarch_gen_fn_ptrs generator; |
| generator.fn_7 = gen_atomic_cas_value_nand_7_si; |
| loongarch_expand_atomic_qihi (generator, operands[0], operands[1], |
| operands[1], operands[2], operands[3]); |
| DONE; |
| }) |