diff --git a/src/cmd/internal/obj/loong64/asm.go b/src/cmd/internal/obj/loong64/asm.go
index f0f8abb59c062b7d24d3fffc160bd97768850169..6b950f8d05b7fe1dac25fe9de90a73e4751b2281 100644
--- a/src/cmd/internal/obj/loong64/asm.go
+++ b/src/cmd/internal/obj/loong64/asm.go
@@ -449,11 +449,8 @@ func span0(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
 	}
 
 	// Run these passes until convergence.
-	bflag := 1
-	var otxt int64
-	var q *obj.Prog
-	for bflag != 0 {
-		bflag = 0
+	for {
+		rescan := false
 		pc = 0
 		prev := c.cursym.Func().Text
 		for p = prev.Link; p != nil; prev, p = p, p.Link {
@@ -468,7 +465,7 @@ func span0(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
 			// because pc will be adjusted if padding happens.
 			if p.Mark&branchLoopHead != 0 && pc&(loopAlign-1) != 0 &&
 				!(prev.As == obj.APCALIGN && prev.From.Offset >= loopAlign) {
-				q = c.newprog()
+				q := c.newprog()
 				prev.Link = q
 				q.Link = p
 				q.Pc = pc
@@ -484,18 +481,29 @@ func span0(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
 				// since this loop iteration is for p.
 				pc += int64(pcAlignPadLength(ctxt, pc, loopAlign))
 				p.Pc = pc
+				rescan = true
 			}
 
 			// very large conditional branches
 			//
-			// if any procedure is large enough to
-			// generate a large SBRA branch, then
-			// generate extra passes putting branches
-			// around jmps to fix. this is rare.
+			// if any procedure is large enough to generate a large SBRA branch, then
+			// generate extra passes putting branches around jmps to fix. this is rare.
 			if o.type_ == 6 && p.To.Target() != nil {
-				otxt = p.To.Target().Pc - pc
-				if otxt < -(1<<17)+10 || otxt >= (1<<17)-10 {
-					q = c.newprog()
+				otxt := p.To.Target().Pc - pc
+
+				// On loong64, the immediate value field of the conditional branch instructions
+				// BFPT and BFPT is 21 bits, and the others are 16 bits. The jump target address
+				// is to logically shift the immediate value in the instruction code to the left
+				// by 2 bits and then sign extend.
+				bound := int64(1 << (18 - 1))
+
+				switch p.As {
+				case ABFPT, ABFPF:
+					bound = int64(1 << (23 - 1))
+				}
+
+				if otxt < -bound || otxt >= bound {
+					q := c.newprog()
 					q.Link = p.Link
 					p.Link = q
 					q.As = AJMP
@@ -510,7 +518,7 @@ func span0(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
 					q.Pos = p.Pos
 					q.To.Type = obj.TYPE_BRANCH
 					q.To.SetTarget(q.Link.Link)
-					bflag = 1
+					rescan = true
 				}
 			}
 
@@ -532,7 +540,12 @@ func span0(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
 		}
 
 		c.cursym.Size = pc
+
+		if !rescan {
+			break
+		}
 	}
+
 	pc += -pc & (FuncAlign - 1)
 	c.cursym.Size = pc
 
diff --git a/src/cmd/internal/obj/loong64/asm_test.go b/src/cmd/internal/obj/loong64/asm_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..a35de61df6ae227a6fd20815b4f24c0762b1d831
--- /dev/null
+++ b/src/cmd/internal/obj/loong64/asm_test.go
@@ -0,0 +1,85 @@
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package loong64
+
+import (
+	"bytes"
+	"fmt"
+	"internal/testenv"
+	"os"
+	"path/filepath"
+	"testing"
+)
+
+const genBufSize = (1024 * 1024 * 32) // 32MB
+
+// TestLargeBranch generates a large function with a very far conditional
+// branch, in order to ensure that it assembles successfully.
+func TestLargeBranch(t *testing.T) {
+	if testing.Short() {
+		t.Skip("Skipping test in short mode")
+	}
+	testenv.MustHaveGoBuild(t)
+
+	dir, err := os.MkdirTemp("", "testlargebranch")
+	if err != nil {
+		t.Fatalf("Could not create directory: %v", err)
+	}
+	defer os.RemoveAll(dir)
+
+	// Generate a very large function.
+	buf := bytes.NewBuffer(make([]byte, 0, genBufSize))
+	genLargeBranch(buf)
+
+	tmpfile := filepath.Join(dir, "x.s")
+	if err := os.WriteFile(tmpfile, buf.Bytes(), 0644); err != nil {
+		t.Fatalf("Failed to write file: %v", err)
+	}
+
+	// Assemble generated file.
+	cmd := testenv.Command(t, testenv.GoToolPath(t), "tool", "asm", "-o", filepath.Join(dir, "x.o"), tmpfile)
+	cmd.Env = append(os.Environ(), "GOARCH=loong64", "GOOS=linux")
+	out, err := cmd.CombinedOutput()
+	if err != nil {
+		t.Errorf("Build failed: %v, output: %s", err, out)
+	}
+}
+
+func genLargeBranch(buf *bytes.Buffer) {
+	genSize1 := (1 << 16) + 16
+	genSize2 := (1 << 21) + 16
+
+	fmt.Fprintln(buf, "TEXT f(SB),0,$0-0")
+	fmt.Fprintln(buf, "BEQ R5, R6, label18")
+	fmt.Fprintln(buf, "BNE R5, R6, label18")
+	fmt.Fprintln(buf, "BGE R5, R6, label18")
+
+	fmt.Fprintln(buf, "BGEU R5, R6, label18")
+	fmt.Fprintln(buf, "BLTU R5, R6, label18")
+
+	fmt.Fprintln(buf, "BLEZ R5, label18")
+	fmt.Fprintln(buf, "BGEZ R5, label18")
+	fmt.Fprintln(buf, "BLTZ R5, label18")
+	fmt.Fprintln(buf, "BGTZ R5, label18")
+
+	fmt.Fprintln(buf, "BFPT label23")
+	fmt.Fprintln(buf, "BFPF label23")
+
+	fmt.Fprintln(buf, "BEQ R5, label23")
+	fmt.Fprintln(buf, "BNE R5, label23")
+
+	for i := 0; i <= genSize1; i++ {
+		fmt.Fprintln(buf, "ADDV $0, R0, R0")
+	}
+
+	fmt.Fprintln(buf, "label18:")
+	for i := 0; i <= (genSize2 - genSize1); i++ {
+		fmt.Fprintln(buf, "ADDV $0, R0, R0")
+	}
+
+	fmt.Fprintln(buf, "label23:")
+	fmt.Fprintln(buf, "ADDV $0, R0, R0")
+	fmt.Fprintln(buf, "RET")
+}