Projects
openEuler:24.03:SP1:Everything
binutils
_service:tar_scm:LoongArch-GAS-Add-support-for-...
Sign Up
Log In
Username
Password
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File _service:tar_scm:LoongArch-GAS-Add-support-for-branch-relaxation.patch of Package binutils
From f9249a44e8e12e90cf5a49729107f69661ed07ec Mon Sep 17 00:00:00 2001 From: mengqinggang <mengqinggang@loongson.cn> Date: Sun, 24 Sep 2023 14:53:28 +0800 Subject: [PATCH 013/123] LoongArch/GAS: Add support for branch relaxation For the instructions of R_LARCH_B16/B21, if the immediate overflow, add a B instruction and R_LARCH_B26 relocation. For example: .L1 ... blt $t0, $t1, .L1 R_LARCH_B16 change to: .L1 ... bge $t0, $t1, .L2 b .L1 R_LARCH_B26 .L2 --- gas/config/tc-loongarch.c | 236 +++++++++++++++--- .../gas/loongarch/la_branch_relax_1.d | 64 +++++ .../gas/loongarch/la_branch_relax_1.s | 33 +++ .../gas/loongarch/la_branch_relax_2.d | 40 +++ .../gas/loongarch/la_branch_relax_2.s | 23 ++ include/opcode/loongarch.h | 12 + 6 files changed, 367 insertions(+), 41 deletions(-) create mode 100644 gas/testsuite/gas/loongarch/la_branch_relax_1.d create mode 100644 gas/testsuite/gas/loongarch/la_branch_relax_1.s create mode 100644 gas/testsuite/gas/loongarch/la_branch_relax_2.d create mode 100644 gas/testsuite/gas/loongarch/la_branch_relax_2.s diff --git a/gas/config/tc-loongarch.c b/gas/config/tc-loongarch.c index 4c48382c..059a1711 100644 --- a/gas/config/tc-loongarch.c +++ b/gas/config/tc-loongarch.c @@ -106,6 +106,16 @@ const char *md_shortopts = "O::g::G:"; static const char default_arch[] = DEFAULT_ARCH; +/* The lowest 4-bit is the bytes of instructions. */ +#define RELAX_BRANCH_16 0xc0000014 +#define RELAX_BRANCH_21 0xc0000024 +#define RELAX_BRANCH_26 0xc0000048 + +#define RELAX_BRANCH(x) \ + (((x) & 0xf0000000) == 0xc0000000) +#define RELAX_BRANCH_ENCODE(x) \ + (BFD_RELOC_LARCH_B16 == (x) ? RELAX_BRANCH_16 : RELAX_BRANCH_21) + enum options { OPTION_IGNORE = OPTION_MD_BASE, @@ -955,11 +965,22 @@ append_fixed_insn (struct loongarch_cl_insn *insn) move_insn (insn, frag_now, f - frag_now->fr_literal); } +/* Add instructions based on the worst-case scenario firstly. */ +static void +append_relaxed_branch_insn (struct loongarch_cl_insn *insn, int max_chars, + int var, relax_substateT subtype, symbolS *symbol, offsetT offset) +{ + frag_grow (max_chars); + move_insn (insn, frag_now, frag_more (0) - frag_now->fr_literal); + frag_var (rs_machine_dependent, max_chars, var, + subtype, symbol, offset, NULL); +} + static void append_fixp_and_insn (struct loongarch_cl_insn *ip) { reloc_howto_type *howto; - bfd_reloc_code_real_type reloc_type; + bfd_reloc_code_real_type r_type; struct reloc_info *reloc_info = ip->reloc_info; size_t i; @@ -967,14 +988,40 @@ append_fixp_and_insn (struct loongarch_cl_insn *ip) for (i = 0; i < ip->reloc_num; i++) { - reloc_type = reloc_info[i].type; - howto = bfd_reloc_type_lookup (stdoutput, reloc_type); - if (howto == NULL) - as_fatal (_("no HOWTO loong relocation number %d"), reloc_type); - - ip->fixp[i] = - fix_new_exp (ip->frag, ip->where, bfd_get_reloc_size (howto), - &reloc_info[i].value, FALSE, reloc_type); + r_type = reloc_info[i].type; + + if (r_type != BFD_RELOC_UNUSED) + { + + gas_assert (&(reloc_info[i].value)); + if (BFD_RELOC_LARCH_B16 == r_type || BFD_RELOC_LARCH_B21 == r_type) + { + int min_bytes = 4; /* One branch instruction. */ + unsigned max_bytes = 8; /* Branch and jump instructions. */ + + if (now_seg == absolute_section) + { + as_bad (_("relaxable branches not supported in absolute section")); + return; + } + + append_relaxed_branch_insn (ip, max_bytes, min_bytes, + RELAX_BRANCH_ENCODE (r_type), + reloc_info[i].value.X_add_symbol, + reloc_info[i].value.X_add_number); + return; + } + else + { + howto = bfd_reloc_type_lookup (stdoutput, r_type); + if (howto == NULL) + as_fatal (_("no HOWTO loong relocation number %d"), r_type); + + ip->fixp[i] = fix_new_exp (ip->frag, ip->where, + bfd_get_reloc_size (howto), + &reloc_info[i].value, FALSE, r_type); + } + } } if (ip->insn_length < ip->relax_max_length) @@ -1489,14 +1536,6 @@ md_apply_fix (fixS *fixP, valueT *valP, segT seg ATTRIBUTE_UNUSED) } } -int -loongarch_relax_frag (asection *sec ATTRIBUTE_UNUSED, - fragS *fragp ATTRIBUTE_UNUSED, - long stretch ATTRIBUTE_UNUSED) -{ - return 0; -} - int md_estimate_size_before_relax (fragS *fragp ATTRIBUTE_UNUSED, asection *segtype ATTRIBUTE_UNUSED) @@ -1528,30 +1567,6 @@ tc_gen_reloc (asection *section ATTRIBUTE_UNUSED, fixS *fixp) return reloc; } -/* Convert a machine dependent frag. */ -void -md_convert_frag (bfd *abfd ATTRIBUTE_UNUSED, segT asec ATTRIBUTE_UNUSED, - fragS *fragp) -{ - expressionS exp; - exp.X_op = O_symbol; - exp.X_add_symbol = fragp->fr_symbol; - exp.X_add_number = fragp->fr_offset; - bfd_byte *buf = (bfd_byte *)fragp->fr_literal + fragp->fr_fix; - - fixS *fixp = fix_new_exp (fragp, buf - (bfd_byte *)fragp->fr_literal, - 4, &exp, false, fragp->fr_subtype); - buf += 4; - - fixp->fx_file = fragp->fr_file; - fixp->fx_line = fragp->fr_line; - fragp->fr_fix += fragp->fr_var; - - gas_assert (fragp->fr_next == NULL - || (fragp->fr_next->fr_address - fragp->fr_address - == fragp->fr_fix)); -} - /* Standard calling conventions leave the CFA at SP on entry. */ void loongarch_cfi_frame_initial_instructions (void) @@ -1777,3 +1792,142 @@ loongarch_elf_final_processing (void) { elf_elfheader (stdoutput)->e_flags = LARCH_opts.ase_abi; } + +/* Compute the length of a branch sequence, and adjust the stored length + accordingly. If FRAGP is NULL, the worst-case length is returned. */ +static unsigned +loongarch_relaxed_branch_length (fragS *fragp, asection *sec, int update) +{ + int length = 4; + + if (!fragp) + return 8; + + if (fragp->fr_symbol != NULL + && S_IS_DEFINED (fragp->fr_symbol) + && !S_IS_WEAK (fragp->fr_symbol) + && sec == S_GET_SEGMENT (fragp->fr_symbol)) + { + offsetT val = S_GET_VALUE (fragp->fr_symbol) + fragp->fr_offset; + + val -= fragp->fr_address + fragp->fr_fix; + + if (RELAX_BRANCH_16 == fragp->fr_subtype + && OUT_OF_RANGE (val, 16, 2)) + { + length = 8; + if (update) + fragp->fr_subtype = RELAX_BRANCH_26; + } + + if (RELAX_BRANCH_21 == fragp->fr_subtype + && OUT_OF_RANGE (val, 21, 2)) + { + length = 8; + if (update) + fragp->fr_subtype = RELAX_BRANCH_26; + } + + if (RELAX_BRANCH_26 == fragp->fr_subtype) + length = 8; + } + + return length; +} + +int +loongarch_relax_frag (asection *sec ATTRIBUTE_UNUSED, + fragS *fragp ATTRIBUTE_UNUSED, + long stretch ATTRIBUTE_UNUSED) +{ + if (RELAX_BRANCH (fragp->fr_subtype)) + { + offsetT old_var = fragp->fr_var; + fragp->fr_var = loongarch_relaxed_branch_length (fragp, sec, true); + return fragp->fr_var - old_var; + } + return 0; +} + +/* Expand far branches to multi-instruction sequences. + Branch instructions: + beq, bne, blt, bgt, bltz, bgtz, ble, bge, blez, bgez + bltu, bgtu, bleu, bgeu + beqz, bnez, bceqz, bcnez. */ + +static void +loongarch_convert_frag_branch (fragS *fragp) +{ + bfd_byte *buf; + expressionS exp; + fixS *fixp; + insn_t insn; + + buf = (bfd_byte *)fragp->fr_literal + fragp->fr_fix; + + exp.X_op = O_symbol; + exp.X_add_symbol = fragp->fr_symbol; + exp.X_add_number = fragp->fr_offset; + + gas_assert ((fragp->fr_subtype & 0xf) == fragp->fr_var); + + /* blt $t0, $t1, .L1 + nop + change to: + bge $t0, $t1, .L2 + b .L1 + .L2: + nop */ + switch (fragp->fr_subtype) + { + case RELAX_BRANCH_26: + insn = bfd_getl32 (buf); + /* Invert the branch condition. */ + if (LARCH_FLOAT_BRANCH == (insn & LARCH_BRANCH_OPCODE_MASK)) + insn ^= LARCH_FLOAT_BRANCH_INVERT_BIT; + else + insn ^= LARCH_BRANCH_INVERT_BIT; + insn |= ENCODE_BRANCH16_IMM (8); /* Set target to PC + 8. */ + bfd_putl32 (insn, buf); + buf += 4; + + /* Add the B instruction and jump to the original target. */ + bfd_putl32 (LARCH_B, buf); + fixp = fix_new_exp (fragp, buf - (bfd_byte *)fragp->fr_literal, + 4, &exp, false, BFD_RELOC_LARCH_B26); + buf += 4; + break; + case RELAX_BRANCH_21: + fixp = fix_new_exp (fragp, buf - (bfd_byte *)fragp->fr_literal, + 4, &exp, false, BFD_RELOC_LARCH_B21); + buf += 4; + break; + case RELAX_BRANCH_16: + fixp = fix_new_exp (fragp, buf - (bfd_byte *)fragp->fr_literal, + 4, &exp, false, BFD_RELOC_LARCH_B16); + buf += 4; + break; + + default: + abort(); + } + + fixp->fx_file = fragp->fr_file; + fixp->fx_line = fragp->fr_line; + + gas_assert (buf == (bfd_byte *)fragp->fr_literal + + fragp->fr_fix + fragp->fr_var); + + fragp->fr_fix += fragp->fr_var; +} + +/* Relax a machine dependent frag. This returns the amount by which + the current size of the frag should change. */ + +void +md_convert_frag (bfd *abfd ATTRIBUTE_UNUSED, segT asec ATTRIBUTE_UNUSED, + fragS *fragp) +{ + gas_assert (RELAX_BRANCH (fragp->fr_subtype)); + loongarch_convert_frag_branch (fragp); +} diff --git a/gas/testsuite/gas/loongarch/la_branch_relax_1.d b/gas/testsuite/gas/loongarch/la_branch_relax_1.d new file mode 100644 index 00000000..7984b6df --- /dev/null +++ b/gas/testsuite/gas/loongarch/la_branch_relax_1.d @@ -0,0 +1,64 @@ +#as: +#objdump: -dr + +.*:[ ]+file format .* + + +Disassembly of section .text: + +0* <.L1>: +[ ]+... +[ ]+48d158:[ ]+5c00098d[ ]+bne[ ]+\$t0, \$t1, 8[ ]+# 48d160 <.L1\+0x48d160> +[ ]+48d15c:[ ]+532ea7ed[ ]+b[ ]+-4772188[ ]+# 0 <.L1> +[ ]+48d15c: R_LARCH_B26[ ]+.L1 +[ ]+48d160:[ ]+5800098d[ ]+beq[ ]+\$t0, \$t1, 8[ ]+# 48d168 <.L1\+0x48d168> +[ ]+48d164:[ ]+532e9fed[ ]+b[ ]+-4772196[ ]+# 0 <.L1> +[ ]+48d164: R_LARCH_B26[ ]+.L1 +[ ]+48d168:[ ]+6400098d[ ]+bge[ ]+\$t0, \$t1, 8[ ]+# 48d170 <.L1\+0x48d170> +[ ]+48d16c:[ ]+532e97ed[ ]+b[ ]+-4772204[ ]+# 0 <.L1> +[ ]+48d16c: R_LARCH_B26[ ]+.L1 +[ ]+48d170:[ ]+640009ac[ ]+bge[ ]+\$t1, \$t0, 8[ ]+# 48d178 <.L1\+0x48d178> +[ ]+48d174:[ ]+532e8fed[ ]+b[ ]+-4772212[ ]+# 0 <.L1> +[ ]+48d174: R_LARCH_B26[ ]+.L1 +[ ]+48d178:[ ]+64000980[ ]+bgez[ ]+\$t0, 8[ ]+# 48d180 <.L1\+0x48d180> +[ ]+48d17c:[ ]+532e87ed[ ]+b[ ]+-4772220[ ]+# 0 <.L1> +[ ]+48d17c: R_LARCH_B26[ ]+.L1 +[ ]+48d180:[ ]+6400080c[ ]+blez[ ]+\$t0, 8[ ]+# 48d188 <.L1\+0x48d188> +[ ]+48d184:[ ]+532e7fed[ ]+b[ ]+-4772228[ ]+# 0 <.L1> +[ ]+48d184: R_LARCH_B26[ ]+.L1 +[ ]+48d188:[ ]+600009ac[ ]+blt[ ]+\$t1, \$t0, 8[ ]+# 48d190 <.L1\+0x48d190> +[ ]+48d18c:[ ]+532e77ed[ ]+b[ ]+-4772236[ ]+# 0 <.L1> +[ ]+48d18c: R_LARCH_B26[ ]+.L1 +[ ]+48d190:[ ]+6000098d[ ]+blt[ ]+\$t0, \$t1, 8[ ]+# 48d198 <.L1\+0x48d198> +[ ]+48d194:[ ]+532e6fed[ ]+b[ ]+-4772244[ ]+# 0 <.L1> +[ ]+48d194: R_LARCH_B26[ ]+.L1 +[ ]+48d198:[ ]+6000080c[ ]+bgtz[ ]+\$t0, 8[ ]+# 48d1a0 <.L1\+0x48d1a0> +[ ]+48d19c:[ ]+532e67ed[ ]+b[ ]+-4772252[ ]+# 0 <.L1> +[ ]+48d19c: R_LARCH_B26[ ]+.L1 +[ ]+48d1a0:[ ]+60000980[ ]+bltz[ ]+\$t0, 8[ ]+# 48d1a8 <.L1\+0x48d1a8> +[ ]+48d1a4:[ ]+532e5fed[ ]+b[ ]+-4772260[ ]+# 0 <.L1> +[ ]+48d1a4: R_LARCH_B26[ ]+.L1 +[ ]+48d1a8:[ ]+6c00098d[ ]+bgeu[ ]+\$t0, \$t1, 8[ ]+# 48d1b0 <.L1\+0x48d1b0> +[ ]+48d1ac:[ ]+532e57ed[ ]+b[ ]+-4772268[ ]+# 0 <.L1> +[ ]+48d1ac: R_LARCH_B26[ ]+.L1 +[ ]+48d1b0:[ ]+6c0009ac[ ]+bgeu[ ]+\$t1, \$t0, 8[ ]+# 48d1b8 <.L1\+0x48d1b8> +[ ]+48d1b4:[ ]+532e4fed[ ]+b[ ]+-4772276[ ]+# 0 <.L1> +[ ]+48d1b4: R_LARCH_B26[ ]+.L1 +[ ]+48d1b8:[ ]+680009ac[ ]+bltu[ ]+\$t1, \$t0, 8[ ]+# 48d1c0 <.L1\+0x48d1c0> +[ ]+48d1bc:[ ]+532e47ed[ ]+b[ ]+-4772284[ ]+# 0 <.L1> +[ ]+48d1bc: R_LARCH_B26[ ]+.L1 +[ ]+48d1c0:[ ]+6800098d[ ]+bltu[ ]+\$t0, \$t1, 8[ ]+# 48d1c8 <.L1\+0x48d1c8> +[ ]+48d1c4:[ ]+532e3fed[ ]+b[ ]+-4772292[ ]+# 0 <.L1> +[ ]+48d1c4: R_LARCH_B26[ ]+.L1 +[ ]+48d1c8:[ ]+44000980[ ]+bnez[ ]+\$t0, 8[ ]+# 48d1d0 <.L1\+0x48d1d0> +[ ]+48d1cc:[ ]+532e37ed[ ]+b[ ]+-4772300[ ]+# 0 <.L1> +[ ]+48d1cc: R_LARCH_B26[ ]+.L1 +[ ]+48d1d0:[ ]+40000980[ ]+beqz[ ]+\$t0, 8[ ]+# 48d1d8 <.L1\+0x48d1d8> +[ ]+48d1d4:[ ]+532e2fed[ ]+b[ ]+-4772308[ ]+# 0 <.L1> +[ ]+48d1d4: R_LARCH_B26[ ]+.L1 +[ ]+48d1d8:[ ]+48000900[ ]+bcnez[ ]+\$fcc0, 8[ ]+# 48d1e0 <.L1\+0x48d1e0> +[ ]+48d1dc:[ ]+532e27ed[ ]+b[ ]+-4772316[ ]+# 0 <.L1> +[ ]+48d1dc: R_LARCH_B26[ ]+.L1 +[ ]+48d1e0:[ ]+48000800[ ]+bceqz[ ]+\$fcc0, 8[ ]+# 48d1e8 <.L1\+0x48d1e8> +[ ]+48d1e4:[ ]+532e1fed[ ]+b[ ]+-4772324[ ]+# 0 <.L1> +[ ]+48d1e4: R_LARCH_B26[ ]+.L1 diff --git a/gas/testsuite/gas/loongarch/la_branch_relax_1.s b/gas/testsuite/gas/loongarch/la_branch_relax_1.s new file mode 100644 index 00000000..53288f25 --- /dev/null +++ b/gas/testsuite/gas/loongarch/la_branch_relax_1.s @@ -0,0 +1,33 @@ +# Instruction and Relocation generating tests + +.L1: + .fill 0x123456, 4, 0x0 + +# R_LARCH_B16 + beq $r12, $r13, .L1 + bne $r12, $r13, .L1 + + blt $r12, $r13, .L1 + bgt $r12, $r13, .L1 + + bltz $r12, .L1 + bgtz $r12, .L1 + + ble $r12, $r13, .L1 + bge $r12, $r13, .L1 + + blez $r12, .L1 + bgez $r12, .L1 + + bltu $r12, $r13, .L1 + bgtu $r12, $r13, .L1 + + bleu $r12, $r13, .L1 + bgeu $r12, $r13, .L1 + +# R_LARCH_B21 + beqz $r12, .L1 + bnez $r12, .L1 + + bceqz $fcc0, .L1 + bcnez $fcc0, .L1 diff --git a/gas/testsuite/gas/loongarch/la_branch_relax_2.d b/gas/testsuite/gas/loongarch/la_branch_relax_2.d new file mode 100644 index 00000000..4a3c6384 --- /dev/null +++ b/gas/testsuite/gas/loongarch/la_branch_relax_2.d @@ -0,0 +1,40 @@ +#as: +#objdump: -dr + +.*:[ ]+file format .* + + +Disassembly of section .text: + +0* <.L1>: +[ ]+... +[ ]+20000:[ ]+5a00018d[ ]+beq[ ]+\$t0, \$t1, -131072[ ]+# 0 <.L1> +[ ]+20000: R_LARCH_B16[ ]+.L1 +[ ]+20004:[ ]+5c00098d[ ]+bne[ ]+\$t0, \$t1, 8[ ]+# 2000c <.L1\+0x2000c> +[ ]+20008:[ ]+51fffbff[ ]+b[ ]+-131080[ ]+# 0 <.L1> +[ ]+20008: R_LARCH_B26[ ]+.L1 +[ ]+2000c:[ ]+5c00098d[ ]+bne[ ]+\$t0, \$t1, 8[ ]+# 20014 <.L1\+0x20014> +[ ]+20010:[ ]+52000000[ ]+b[ ]+131072[ ]+# 40010 <.L2> +[ ]+20010: R_LARCH_B26[ ]+.L2 +[ ]+20014:[ ]+59fffd8d[ ]+beq[ ]+\$t0, \$t1, 131068[ ]+# 40010 <.L2> +[ ]+20014: R_LARCH_B16[ ]+.L2 +[ ]+... +0*40010 <.L2>: +[ ]+... +[ ]+440010:[ ]+40000190[ ]+beqz[ ]+\$t0, -4194304[ ]+# 40010 <.L2> +[ ]+440010: R_LARCH_B21[ ]+.L2 +[ ]+440014:[ ]+44000980[ ]+bnez[ ]+\$t0, 8[ ]+# 44001c <.L2\+0x40000c> +[ ]+440018:[ ]+53fffbef[ ]+b[ ]+-4194312[ ]+# 40010 <.L2> +[ ]+440018: R_LARCH_B26[ ]+.L2 +[ ]+44001c:[ ]+44000980[ ]+bnez[ ]+\$t0, 8[ ]+# 440024 <.L2\+0x400014> +[ ]+440020:[ ]+50000010[ ]+b[ ]+4194304[ ]+# 840020 <.L3> +[ ]+440020: R_LARCH_B26[ ]+.L3 +[ ]+440024:[ ]+43fffd8f[ ]+beqz[ ]+\$t0, 4194300[ ]+# 840020 <.L3> +[ ]+440024: R_LARCH_B21[ ]+.L3 +[ ]+... +0*840020 <.L3>: +[ ]+840020:[ ]+5800018d[ ]+beq[ ]+\$t0, \$t1, 0[ ]+# 840020 <.L3> +[ ]+840020: R_LARCH_B16[ ]+.L4 +0*840024 <.L5>: +[ ]+840024:[ ]+40000180[ ]+beqz[ ]+\$t0, 0[ ]+# 840024 <.L5> +[ ]+840024: R_LARCH_B21[ ]+.L5 diff --git a/gas/testsuite/gas/loongarch/la_branch_relax_2.s b/gas/testsuite/gas/loongarch/la_branch_relax_2.s new file mode 100644 index 00000000..3e6c5534 --- /dev/null +++ b/gas/testsuite/gas/loongarch/la_branch_relax_2.s @@ -0,0 +1,23 @@ +# Immediate boundary value tests + +.L1: + .fill 0x8000, 4, 0 + beq $r12, $r13, .L1 # min imm -0x20000 + beq $r12, $r13, .L1 # out of range + beq $r12, $r13, .L2 # out of range + beq $r12, $r13, .L2 # max imm 0x1fffc + .fill 0x7ffe, 4, 0 +.L2: + .fill 0x100000, 4, 0 + beqz $r12, .L2 # min imm -0x400000 + beqz $r12, .L2 # out of range + beqz $r12, .L3 # out of range + beqz $r12, .L3 # max imm 0x3ffffc + .fill 0xffffe, 4, 0 +.L3: + +# 0 imm +.L4: + beq $r12, $r13, .L4 +.L5: + beqz $r12, .L5 diff --git a/include/opcode/loongarch.h b/include/opcode/loongarch.h index 2ed4082c..f358ff42 100644 --- a/include/opcode/loongarch.h +++ b/include/opcode/loongarch.h @@ -29,6 +29,18 @@ extern "C" #endif #define LARCH_NOP 0x03400000 + #define LARCH_B 0x50000000 + /* BCEQZ/BCNEZ. */ + #define LARCH_FLOAT_BRANCH 0x48000000 + #define LARCH_BRANCH_OPCODE_MASK 0xfc000000 + #define LARCH_BRANCH_INVERT_BIT 0x04000000 + #define LARCH_FLOAT_BRANCH_INVERT_BIT 0x00000100 + + #define ENCODE_BRANCH16_IMM(x) (((x) >> 2) << 10) + + #define OUT_OF_RANGE(value, bits, align) \ + ((value) < (-(1 << ((bits) - 1) << align)) \ + || (value) > ((((1 << ((bits) - 1)) - 1) << align))) typedef uint32_t insn_t; -- 2.33.0
Locations
Projects
Search
Status Monitor
Help
Open Build Service
OBS Manuals
API Documentation
OBS Portal
Reporting a Bug
Contact
Mailing List
Forums
Chat (IRC)
Twitter
Open Build Service (OBS)
is an
openSUSE project
.
浙ICP备2022010568号-2