Index: src/cmd/internal/obj/riscv/obj.go
--- src/cmd/internal/obj/riscv/obj.go.orig
+++ src/cmd/internal/obj/riscv/obj.go
@@ -1264,6 +1264,11 @@ func encodeSF(ins *instruction) uint32 {
 	return encodeS(ins.as, regI(ins.rd), regF(ins.rs1), uint32(ins.imm))
 }
 
+// encodeBImmediate encodes an immediate for a B-type RISC-V instruction.
+func encodeBImmediate(imm uint32) uint32 {
+	return (imm>>12)<<31 | ((imm>>5)&0x3f)<<25 | ((imm>>1)&0xf)<<8 | ((imm>>11)&0x1)<<7
+}
+
 // encodeB encodes a B-type RISC-V instruction.
 func encodeB(ins *instruction) uint32 {
 	imm := immI(ins.as, ins.imm, 13)
@@ -1273,7 +1278,7 @@ func encodeB(ins *instruction) uint32 {
 	if enc == nil {
 		panic("encodeB: could not encode instruction")
 	}
-	return (imm>>12)<<31 | ((imm>>5)&0x3f)<<25 | rs2<<20 | rs1<<15 | enc.funct3<<12 | ((imm>>1)&0xf)<<8 | ((imm>>11)&0x1)<<7 | enc.opcode
+	return encodeBImmediate(imm) | rs2<<20 | rs1<<15 | enc.funct3<<12 | enc.opcode
 }
 
 // encodeU encodes a U-type RISC-V instruction.
@@ -1316,16 +1321,67 @@ func encodeRawIns(ins *instruction) uint32 {
 	return uint32(ins.imm)
 }
 
-func EncodeJImmediate(imm int64) (int64, error) {
-	if !immIFits(imm, 21) {
-		return 0, fmt.Errorf("immediate %#x does not fit in 21 bits", imm)
+func extractAndShift(imm int64, bit, pos int) int64 {
+	return ((imm >> (bit - 1)) & 1) << (pos - 1)
+}
+
+func EncodeBImmediate(imm int64) (int64, error) {
+	if !immIFits(imm, 13) {
+		return 0, fmt.Errorf("immediate %#x does not fit in 13 bits", imm)
 	}
 	if imm&1 != 0 {
 		return 0, fmt.Errorf("immediate %#x is not a multiple of two", imm)
 	}
-	return int64(encodeJImmediate(uint32(imm))), nil
+	return int64(encodeBImmediate(uint32(imm))), nil
 }
 
+func EncodeCBImmediate(imm int64) (int64, error) {
+	if !immIFits(imm, 9) {
+		return 0, fmt.Errorf("immediate %#x does not fit in 9 bits", imm)
+	}
+	if imm&1 != 0 {
+		return 0, fmt.Errorf("immediate %#x is not a multiple of two", imm)
+	}
+	imm = imm >> 1
+
+	// Bit order - [8|4:3|7:6|2:1|5]
+	bits := extractAndShift(imm, 8, 8)
+	bits |= extractAndShift(imm, 4, 7)
+	bits |= extractAndShift(imm, 3, 6)
+	bits |= extractAndShift(imm, 7, 5)
+	bits |= extractAndShift(imm, 6, 4)
+	bits |= extractAndShift(imm, 2, 3)
+	bits |= extractAndShift(imm, 1, 2)
+	bits |= extractAndShift(imm, 5, 1)
+
+	return (bits>>5)<<10 | (bits&0x1f)<<2, nil
+}
+
+func EncodeCJImmediate(imm int64) (int64, error) {
+	if !immIFits(imm, 12) {
+		return 0, fmt.Errorf("immediate %#x does not fit in 12 bits", imm)
+	}
+	if imm&1 != 0 {
+		return 0, fmt.Errorf("immediate %#x is not a multiple of two", imm)
+	}
+	imm = imm >> 1
+
+	// Bit order - [11|4|9:8|10|6|7|3:1|5]
+	bits := extractAndShift(imm, 11, 11)
+	bits |= extractAndShift(imm, 4, 10)
+	bits |= extractAndShift(imm, 9, 9)
+	bits |= extractAndShift(imm, 8, 8)
+	bits |= extractAndShift(imm, 10, 7)
+	bits |= extractAndShift(imm, 6, 6)
+	bits |= extractAndShift(imm, 7, 5)
+	bits |= extractAndShift(imm, 3, 4)
+	bits |= extractAndShift(imm, 2, 3)
+	bits |= extractAndShift(imm, 1, 2)
+	bits |= extractAndShift(imm, 5, 1)
+
+	return bits << 2, nil
+}
+
 func EncodeIImmediate(imm int64) (int64, error) {
 	if !immIFits(imm, 12) {
 		return 0, fmt.Errorf("immediate %#x does not fit in 12 bits", imm)
@@ -1333,6 +1389,16 @@ func EncodeIImmediate(imm int64) (int64, error) {
 	return imm << 20, nil
 }
 
+func EncodeJImmediate(imm int64) (int64, error) {
+	if !immIFits(imm, 21) {
+		return 0, fmt.Errorf("immediate %#x does not fit in 21 bits", imm)
+	}
+	if imm&1 != 0 {
+		return 0, fmt.Errorf("immediate %#x is not a multiple of two", imm)
+	}
+	return int64(encodeJImmediate(uint32(imm))), nil
+}
+
 func EncodeSImmediate(imm int64) (int64, error) {
 	if !immIFits(imm, 12) {
 		return 0, fmt.Errorf("immediate %#x does not fit in 12 bits", imm)
@@ -1819,6 +1885,53 @@ func instructionsForStore(p *obj.Prog, as obj.As, rd i
 	return []*instruction{insLUI, insADD, ins}
 }
 
+func instructionsForTLS(p *obj.Prog, ins *instruction) []*instruction {
+	insAddTP := &instruction{as: AADD, rd: REG_TMP, rs1: REG_TMP, rs2: REG_TP}
+
+	var inss []*instruction
+	if p.Ctxt.Flag_shared {
+		// TLS initial-exec mode - load TLS offset from GOT, add the thread pointer
+		// register, then load from or store to the resulting memory location.
+		insAUIPC := &instruction{as: AAUIPC, rd: REG_TMP}
+		insLoadTLSOffset := &instruction{as: ALD, rd: REG_TMP, rs1: REG_TMP}
+		inss = []*instruction{insAUIPC, insLoadTLSOffset, insAddTP, ins}
+	} else {
+		// TLS local-exec mode - load upper TLS offset, add the lower TLS offset,
+		// add the thread pointer register, then load from or store to the resulting
+		// memory location. Note that this differs from the suggested three
+		// instruction sequence, as the Go linker does not currently have an
+		// easy way to handle relocation across 12 bytes of machine code.
+		insLUI := &instruction{as: ALUI, rd: REG_TMP}
+		insADDIW := &instruction{as: AADDIW, rd: REG_TMP, rs1: REG_TMP}
+		inss = []*instruction{insLUI, insADDIW, insAddTP, ins}
+	}
+	return inss
+}
+
+func instructionsForTLSLoad(p *obj.Prog) []*instruction {
+	if p.From.Sym.Type != objabi.STLSBSS {
+		p.Ctxt.Diag("%v: %v is not a TLS symbol", p, p.From.Sym)
+		return nil
+	}
+
+	ins := instructionForProg(p)
+	ins.as, ins.rs1, ins.rs2, ins.imm = movToLoad(p.As), REG_TMP, obj.REG_NONE, 0
+
+	return instructionsForTLS(p, ins)
+}
+
+func instructionsForTLSStore(p *obj.Prog) []*instruction {
+	if p.To.Sym.Type != objabi.STLSBSS {
+		p.Ctxt.Diag("%v: %v is not a TLS symbol", p, p.To.Sym)
+		return nil
+	}
+
+	ins := instructionForProg(p)
+	ins.as, ins.rd, ins.rs1, ins.rs2, ins.imm = movToStore(p.As), REG_TMP, uint32(p.From.Reg), obj.REG_NONE, 0
+
+	return instructionsForTLS(p, ins)
+}
+
 // instructionsForMOV returns the machine instructions for an *obj.Prog that
 // uses a MOV pseudo-instruction.
 func instructionsForMOV(p *obj.Prog) []*instruction {
@@ -1908,6 +2021,10 @@ func instructionsForMOV(p *obj.Prog) []*instruction {
 			inss = instructionsForLoad(p, movToLoad(p.As), addrToReg(p.From))
 
 		case obj.NAME_EXTERN, obj.NAME_STATIC:
+			if p.From.Sym.Type == objabi.STLSBSS {
+				return instructionsForTLSLoad(p)
+			}
+
 			// Note that the values for $off_hi and $off_lo are currently
 			// zero and will be assigned during relocation.
 			//
@@ -1935,6 +2052,10 @@ func instructionsForMOV(p *obj.Prog) []*instruction {
 			inss = instructionsForStore(p, movToStore(p.As), addrToReg(p.To))
 
 		case obj.NAME_EXTERN, obj.NAME_STATIC:
+			if p.To.Sym.Type == objabi.STLSBSS {
+				return instructionsForTLSStore(p)
+			}
+
 			// Note that the values for $off_hi and $off_lo are currently
 			// zero and will be assigned during relocation.
 			//
@@ -2198,10 +2319,10 @@ func assemble(ctxt *obj.Link, cursym *obj.LSym, newpro
 				break
 			}
 			if addr.Sym.Type == objabi.STLSBSS {
-				if rt == objabi.R_RISCV_PCREL_ITYPE {
-					rt = objabi.R_RISCV_TLS_IE_ITYPE
-				} else if rt == objabi.R_RISCV_PCREL_STYPE {
-					rt = objabi.R_RISCV_TLS_IE_STYPE
+				if ctxt.Flag_shared {
+					rt = objabi.R_RISCV_TLS_IE
+				} else {
+					rt = objabi.R_RISCV_TLS_LE
 				}
 			}
 
