This is an archive of the discontinued LLVM Phabricator instance.

[X86] Preserve redundant Address-Size override prefix
ClosedPublic

Authored by Amir on Feb 25 2022, 3:05 PM.

Details

Summary

Print and emit redundant Address-Size override prefix if it's set on the
instruction.

Diff Detail

Event Timeline

Amir created this revision.Feb 25 2022, 3:05 PM
Amir requested review of this revision.Feb 25 2022, 3:05 PM
Herald added a project: Restricted Project. · View Herald TranscriptFeb 25 2022, 3:05 PM
skan requested changes to this revision.Feb 27 2022, 8:28 PM

I think this patch is totally wrong. We need address size override prefix only when the instruction has a memory operand.The address-size override prefix (67H) allows programs to switch between 16- and 32-bit addressing. Either size can be the default; the prefix selects the non-default size. Using this prefix and/or other undefined opcodes when operands for the instruction do not reside in memory is reserved; such use may cause unpredictable behavior. Whether a instruction need ASZ should be included in the definition of the instruction in tablgen.

This revision now requires changes to proceed.Feb 27 2022, 8:28 PM

I think this patch is totally wrong. We need address size override prefix only when the instruction has a memory operand.The address-size override prefix (67H) allows programs to switch between 16- and 32-bit addressing. Either size can be the default; the prefix selects the non-default size. Using this prefix and/or other undefined opcodes when operands for the instruction do not reside in memory is reserved; such use may cause unpredictable behavior. Whether a instruction need ASZ should be included in the definition of the instruction in tablgen.

I believe example test case is supported by gas and callq does use address size. What syntax should be used instead?

skan added a comment.Feb 27 2022, 9:54 PM

I think this patch is totally wrong. We need address size override prefix only when the instruction has a memory operand.The address-size override prefix (67H) allows programs to switch between 16- and 32-bit addressing. Either size can be the default; the prefix selects the non-default size. Using this prefix and/or other undefined opcodes when operands for the instruction do not reside in memory is reserved; such use may cause unpredictable behavior. Whether a instruction need ASZ should be included in the definition of the instruction in tablgen.

I believe example test case is supported by gas and callq does use address size. What syntax should be used instead?

This test case is misleading. LLVM-MC can pass the test case w/o this patch b/c an expicit prefix is added. However, this callq does not need a ASZ at all.

I think this patch is totally wrong. We need address size override prefix only when the instruction has a memory operand.The address-size override prefix (67H) allows programs to switch between 16- and 32-bit addressing. Either size can be the default; the prefix selects the non-default size. Using this prefix and/or other undefined opcodes when operands for the instruction do not reside in memory is reserved; such use may cause unpredictable behavior. Whether a instruction need ASZ should be included in the definition of the instruction in tablgen.

I believe example test case is supported by gas and callq does use address size. What syntax should be used instead?

This test case is misleading. LLVM-MC can pass the test case w/o this patch b/c an expicit prefix is added. However, this callq does not need a ASZ at all.

There is a legimate use for a redundant addr32 on a call instruction. See this code in lld

// Convert call/jmp instructions.
if (modRm == 0x15) {
  // ABI says we can convert "call *foo@GOTPCREL(%rip)" to "nop; call foo".
  // Instead we convert to "addr32 call foo" where addr32 is an instruction
  // prefix. That makes result expression to be a single instruction.
  loc[-2] = 0x67; // addr32 prefix
  loc[-1] = 0xe8; // call
  write32le(loc, val);
  return;
}
skan added a comment.Feb 28 2022, 12:57 AM

I think this patch is totally wrong. We need address size override prefix only when the instruction has a memory operand.The address-size override prefix (67H) allows programs to switch between 16- and 32-bit addressing. Either size can be the default; the prefix selects the non-default size. Using this prefix and/or other undefined opcodes when operands for the instruction do not reside in memory is reserved; such use may cause unpredictable behavior. Whether a instruction need ASZ should be included in the definition of the instruction in tablgen.

I believe example test case is supported by gas and callq does use address size. What syntax should be used instead?

This test case is misleading. LLVM-MC can pass the test case w/o this patch b/c an expicit prefix is added. However, this callq does not need a ASZ at all.

There is a legimate use for a redundant addr32 on a call instruction. See this code in lld

// Convert call/jmp instructions.
if (modRm == 0x15) {
  // ABI says we can convert "call *foo@GOTPCREL(%rip)" to "nop; call foo".
  // Instead we convert to "addr32 call foo" where addr32 is an instruction
  // prefix. That makes result expression to be a single instruction.
  loc[-2] = 0x67; // addr32 prefix
  loc[-1] = 0xe8; // call
  write32le(loc, val);
  return;
}

Thanks craig, I agree that we can add a redundant addr32 prefix on callq.

In addition, and GNU objdump also prints the prefix even if it is illegal, which can help developer find their bug.

cat 1.s

addr32 ret

as 1.s -o 1.o && objdump -d 1.o

0000000000000000 <.text>:
   0:   67 c3                   addr32 retq

This change looks good to me. But the test case should be moved into MC and covers ATT/Intel assemble syntax.

skan added inline comments.Feb 28 2022, 1:12 AM
bolt/test/X86/addr32.s
3–21 ↗(On Diff #411531)
skan added inline comments.Feb 28 2022, 1:13 AM
bolt/test/X86/addr32.s
1 ↗(On Diff #411531)

This test should be moved to llvm/test/MC/Disassembler/X86/

Amir added a comment.Feb 28 2022, 11:13 AM

@skan, @craig.topper
Thank you for the review! I didn't communicate the intent clearly. There's a BOLT bug report (https://github.com/llvm/llvm-project/issues/54066#issuecomment-1050327548) in which BOLT removes addr32 prefix from call imm. I've root caused it to instruction printer and emitter ignoring the explicit (redundant) prefix. The code is produced by g++ (GCC) 9.2.1 20200225, with -O3 -g -std=gnu++17 -Wl,-Bsymbolic-functions -Wl,--dynamic-list-cpp-new. The linker wasn't mentioned.

skan added a comment.EditedFeb 28 2022, 4:55 PM

@skan, @craig.topper
Thank you for the review! I didn't communicate the intent clearly. There's a BOLT bug report (https://github.com/llvm/llvm-project/issues/54066#issuecomment-1050327548) in which BOLT removes addr32 prefix from call imm. I've root caused it to instruction printer and emitter ignoring the explicit (redundant) prefix. The code is produced by g++ (GCC) 9.2.1 20200225, with -O3 -g -std=gnu++17 -Wl,-Bsymbolic-functions -Wl,--dynamic-list-cpp-new. The linker wasn't mentioned.

I didn't apply the patch and rebuild the compiler. But I think the bug can be reproduced w/o BOLT, right?

Amir updated this revision to Diff 411935.Feb 28 2022, 5:29 PM

Fix tests

Amir added a comment.Feb 28 2022, 5:31 PM

@skan, @craig.topper
Thank you for the review! I didn't communicate the intent clearly. There's a BOLT bug report (https://github.com/llvm/llvm-project/issues/54066#issuecomment-1050327548) in which BOLT removes addr32 prefix from call imm. I've root caused it to instruction printer and emitter ignoring the explicit (redundant) prefix. The code is produced by g++ (GCC) 9.2.1 20200225, with -O3 -g -std=gnu++17 -Wl,-Bsymbolic-functions -Wl,--dynamic-list-cpp-new. The linker wasn't mentioned.

I didn't apply the patch and rebuild the compiler. But I think the bug can be reproduced w/o BOLT, right?

Yes, it reproduces with llvm-mc disasm-asm roundtrip (the prefix is lost).

skan added inline comments.Feb 28 2022, 5:39 PM
llvm/test/MC/X86/align-branch-hardcode.s
20 ↗(On Diff #411935)

Why do we got addr32 here w/ hardcode 0x66?

Amir added inline comments.Feb 28 2022, 5:52 PM
llvm/test/MC/X86/align-branch-hardcode.s
20 ↗(On Diff #411935)

Looking into it

Amir added inline comments.Feb 28 2022, 7:09 PM
llvm/test/MC/X86/align-branch-hardcode.s
20 ↗(On Diff #411935)

@skan
The call has 0x67 prefix regardless of .byte 0x66, perhaps due to using ecx register as a memop.
With this diff we start printing the addr32 prefix that wasn't printed before.

skan added inline comments.Feb 28 2022, 8:23 PM
llvm/test/MC/X86/code16-32-64.s
18–20 ↗(On Diff #411938)

addr32 should not be printed here b/c 0x67 is not a redundant prefix here. You can check the behaviour of GNU objdump.

yota9 removed a subscriber: yota9.Mar 1 2022, 7:53 AM
Amir updated this revision to Diff 412323.Mar 1 2022, 10:30 PM

Only print address size override prefix in disassembly when it's redundant

Herald added a project: Restricted Project. · View Herald TranscriptMar 1 2022, 10:30 PM
Amir added a comment.Mar 1 2022, 10:34 PM

@skan
I moved NeedAddressOverride check out of X86MCCodeEmitter.cpp (along with isXXBitMemOperand) into X86MCTargetDesc in order to use it in printInstFlags to test if addr32 is a redundant prefix.

Amir retitled this revision from [X86] Fix handling of Address-Size override prefix to [X86] Preserve redundant Address-Size override prefix.Mar 1 2022, 10:56 PM
Amir edited the summary of this revision. (Show Details)
skan added a comment.Mar 1 2022, 11:04 PM

@skan
I moved NeedAddressOverride check out of X86MCCodeEmitter.cpp (along with isXXBitMemOperand) into X86MCTargetDesc in order to use it in printInstFlags to test if addr32 is a redundant prefix.

I belive it's the correct direction. However there is still a bug. (see my inline comments)

llvm/test/MC/Disassembler/X86/prefixes.txt
54 ↗(On Diff #412323)

0x67 is not redundant here. It changes (%rdi) to (%edi). You can not encode
rep addr32 stosq %rax, %es:(%edi) w/ llvm-mc --show-encoding

llvm/test/MC/X86/addr16-32.s
20–23 ↗(On Diff #412323)

0x67 is not redundant here.

Amir updated this revision to Diff 412564.Mar 2 2022, 3:39 PM

Moved RawFrmSrc, RawFrmDstSrc, RawFrmDst handling from X86MCCodeEmitter::emitPrefixImpl
to X86_MC::needsAddressSizeOverride. This consolidates all checks controlling
the emission of AddressSize override prefix in needsAddressSizeOverride.

The flipside is a prefix order change but it should be benign.

Amir marked 2 inline comments as done.Mar 2 2022, 3:39 PM
Amir updated this revision to Diff 412565.Mar 2 2022, 3:44 PM

Code cleanup

craig.topper added inline comments.Mar 2 2022, 3:56 PM
llvm/lib/Target/X86/MCTargetDesc/X86MCTargetDesc.cpp
75

Missing namespace? Or missing static?

llvm/test/MC/X86/index-operations.s
74

Was this a bug before? REX prefix is supposed to be the byte before the opcode.

Amir added inline comments.Mar 2 2022, 4:23 PM
llvm/test/MC/X86/index-operations.s
74

You're right. It was a bug before, but it's still there:
REX prefix emission happened before emitSegmentOverridePrefix and 0x67 prefix, this diff moved 0x67 prefix emission above.
See the line
HasREX = emitOpcodePrefix(MemoryOperand, MI, STI, OS);

Amir updated this revision to Diff 412573.Mar 2 2022, 4:28 PM

static isMemOperand

Amir marked an inline comment as done.Mar 2 2022, 4:29 PM
skan accepted this revision.Mar 2 2022, 11:05 PM
skan added a reviewer: craig.topper.

LGTM.

This revision is now accepted and ready to land.Mar 2 2022, 11:06 PM
Amir marked an inline comment as done.Mar 3 2022, 10:59 AM
Amir added inline comments.
llvm/test/MC/X86/index-operations.s
74

Addressed this issue in D120871 (follow-up to this diff)

This revision was landed with ongoing or failed builds.Mar 16 2022, 8:30 AM
This revision was automatically updated to reflect the committed changes.