// Copyright 2025 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 asmgen var ArchAMD64 = &Arch{ Name: "amd64", WordBits: 64, WordBytes: 8, regs: []string{ "BX", "SI", "DI", "R8", "R9", "R10", "R11", "R12", "R13", "R14", "R15", "AX", "DX", "CX", // last to leave available for hinted allocation }, op3: x86Op3, hint: x86Hint, memOK: true, subCarryIsBorrow: true, // Note: Not setting memIndex, because code generally runs faster // if we avoid the use of scaled-index memory references, // particularly in ADX instructions. options: map[Option]func(*Asm, string){ OptionAltCarry: amd64JmpADX, }, mov: "MOVQ", adds: "ADDQ", adcs: "ADCQ", subs: "SUBQ", sbcs: "SBBQ", lsh: "SHLQ", lshd: "SHLQ", rsh: "SHRQ", rshd: "SHRQ", and: "ANDQ", or: "ORQ", xor: "XORQ", neg: "NEGQ", lea: "LEAQ", addF: amd64Add, mulWideF: x86MulWide, addWords: "LEAQ (%[2]s)(%[1]s*8), %[3]s", jmpZero: "TESTQ %[1]s, %[1]s; JZ %[2]s", jmpNonZero: "TESTQ %[1]s, %[1]s; JNZ %[2]s", loopBottom: "SUBQ $1, %[1]s; JNZ %[2]s", loopBottomNeg: "ADDQ $1, %[1]s; JNZ %[2]s", } func amd64JmpADX(a *Asm, label string) { a.Printf("\tCMPB ·hasADX(SB), $0; JNZ %s\n", label) } func amd64Add(a *Asm, src1, src2 Reg, dst Reg, carry Carry) bool { if a.Enabled(OptionAltCarry) { // If OptionAltCarry is enabled, the generator is emitting ADD instructions // both with and without the AltCarry flag set; the AltCarry flag means to // use ADOX. Otherwise we have to use ADCX. // Using regular ADD/ADC would smash both carry flags, // so we reject anything we can't handled with ADCX/ADOX. if carry&UseCarry != 0 && carry&(SetCarry|SmashCarry) != 0 { if carry&AltCarry != 0 { a.op3("ADOXQ", src1, src2, dst) } else { a.op3("ADCXQ", src1, src2, dst) } return true } if carry&(SetCarry|UseCarry) == SetCarry && a.IsZero(src1) && src2 == dst { // Clearing carry flag. Caller will add EOL comment. a.Printf("\tTESTQ AX, AX\n") return true } if carry != KeepCarry { a.Fatalf("unsupported carry") } } return false } // The x86-prefixed functions are shared with Arch386 in 386.go. func x86Op3(name string) bool { // As far as a.op3 is concerned, there are no 3-op instructions. // (We print instructions like MULX ourselves.) return false } func x86Hint(a *Asm, h Hint) string { switch h { case HintShiftCount: return "CX" case HintMulSrc: if a.Enabled(OptionAltCarry) { // using MULX return "DX" } return "AX" case HintMulHi: if a.Enabled(OptionAltCarry) { // using MULX return "" } return "DX" } return "" } func x86Suffix(a *Asm) string { // Note: Not using a.Arch == Arch386 to avoid init cycle. if a.Arch.Name == "386" { return "L" } return "Q" } func x86MulWide(a *Asm, src1, src2, dstlo, dsthi Reg) { if a.Enabled(OptionAltCarry) { // Using ADCX/ADOX; use MULX to avoid clearing carry flag. if src1.name != "DX" { if src2.name != "DX" { a.Fatalf("mul src1 or src2 must be DX") } src2 = src1 } a.Printf("\tMULXQ %s, %s, %s\n", src2, dstlo, dsthi) return } if src1.name != "AX" { if src2.name != "AX" { a.Fatalf("mulwide src1 or src2 must be AX") } src2 = src1 } if dstlo.name != "AX" { a.Fatalf("mulwide dstlo must be AX") } if dsthi.name != "DX" { a.Fatalf("mulwide dsthi must be DX") } a.Printf("\tMUL%s %s\n", x86Suffix(a), src2) }