This is an archive of the discontinued LLVM Phabricator instance.

[X86][MC][NFC] Refine code in X86MCCodeEmitter.cpp about opcode prefix
ClosedPublic

Authored by skan on Feb 7 2023, 12:30 AM.

Details

Summary
  1. Make code clearer by separating the logic of setting bits from the logic of how a prefix is encoded
  2. Extract common code into functions to avoid code duplication
  3. Return a enum rather a boolean to ehance scalability and uniform the behavior of functions

Diff Detail

Event Timeline

skan created this revision.Feb 7 2023, 12:30 AM
Herald added a project: Restricted Project. · View Herald TranscriptFeb 7 2023, 12:30 AM
skan requested review of this revision.Feb 7 2023, 12:30 AM
Herald added a project: Restricted Project. · View Herald TranscriptFeb 7 2023, 12:30 AM
craig.topper added inline comments.Feb 7 2023, 12:45 AM
llvm/lib/Target/X86/MCTargetDesc/X86MCCodeEmitter.cpp
222

Woudln't this be better as (W | R | X | B) ? REX : None?

224

Similar here.

283–284

clang-format

840–842

The ?: can just be combined into the return here. No need for a variable.

skan added inline comments.Feb 7 2023, 12:49 AM
llvm/lib/Target/X86/MCTargetDesc/X86MCCodeEmitter.cpp
222

Good idea!

224

Will do.

840–842

Good point!

craig.topper added inline comments.Feb 7 2023, 12:56 AM
llvm/lib/Target/X86/MCTargetDesc/X86MCCodeEmitter.cpp
1278

I don't know that it makes sense to make returning the bool for HasREX worse by spreading it to more functions. None of the other prefixes are needed and I'm not sure there's sufficient evidence they ever will be. And if they were needed would what type of prefix be enough information?

A bool &HasREX passed to emitOpcodePrefix and emitREXPrefix feels cleaner to me. I never liked the returned bool for this.

skan updated this revision to Diff 495406.Feb 7 2023, 12:59 AM
skan marked 4 inline comments as done.

Address review comments

skan added inline comments.Feb 7 2023, 1:22 AM
llvm/lib/Target/X86/MCTargetDesc/X86MCCodeEmitter.cpp
1278

I know a little about it. When X86 introduces a bunch of new instructions, it usually extends the prefix. For example,
when moved from ia32 to ia32e, REX was defined. Similarly, when we moved from SSE to AVX, VEX prefix was defined.

And when we introduce new instructions, a new relocation may be needed. Here is the example for REX
https://groups.google.com/g/x86-64-abi/c/n9AWHogmVY0

Linker can do relocation optimization based on the relocation. From my understanding, if no new relocation were not added for the new instructions, the optimization could be done in an incorrect way silently.

Only a bool HasREX was defined here b/c the current interested instructions are only REX-encoded.
But we should allow more possibility here. And returning a enum here is almost as cheap as bool.

skan added inline comments.Feb 7 2023, 1:30 AM
llvm/lib/Target/X86/MCTargetDesc/X86MCCodeEmitter.cpp
1278

Got your idea. Let me to pass the enum by reference rather than a return value.

craig.topper added inline comments.Feb 7 2023, 1:37 AM
llvm/lib/Target/X86/MCTargetDesc/X86MCCodeEmitter.cpp
1278

REX is older than the VEX, XOP, EVEX prefixes so it doesn’t seem common to need the new relocation.

It was cheaper to compute I’d say we could determine if it has REX in the fixup code but we have to inspect multiple operands.

If we need to know about a prefix for a future location that one might be cheaper to compute.

This feels like we’re trying to solve a future problem that might never exist. Is it really worth it?

1278

*relocation

craig.topper added inline comments.Feb 7 2023, 1:46 AM
llvm/lib/Target/X86/MCTargetDesc/X86MCCodeEmitter.cpp
1278

It’s also hard to add new relocations anyway since you would need a new linker that understands it. Or a command line to opt into it.

skan added inline comments.Feb 7 2023, 2:00 AM
llvm/lib/Target/X86/MCTargetDesc/X86MCCodeEmitter.cpp
1278

I just gave a try. But passing the enum as argument increases the length of arglists for four functions: emitREXPrefix, emitVEXOpcodePrefix, emitOpcodePrefix and emitPrefixImpl longer, which made me nervous. I don't even know which order I should use for the parameters for the functions.

I remembered that we once refined the code in this file to reduce the parameters, so I prefer the "return value" version.
AFAICS, whether the prefix is REX, VEX, or EVEX should be enough information for the other code in emitInstructiion.

skan added inline comments.Feb 7 2023, 2:04 AM
llvm/lib/Target/X86/MCTargetDesc/X86MCCodeEmitter.cpp
1278

Yes, I was talking about that if we know more info about prefix, we can easily extend the relocation type in X86MCCodeEmitter::emitMemModRMByte.

Yikarus added a subscriber: Yikarus.Feb 7 2023, 5:20 AM
Yikarus added inline comments.
llvm/lib/Target/X86/MCTargetDesc/X86MCCodeEmitter.cpp
782–783

Need rewrite the comment

skan updated this revision to Diff 495507.Feb 7 2023, 6:25 AM
skan marked an inline comment as done.

Address review comments

skan added inline comments.Feb 7 2023, 6:31 AM
llvm/lib/Target/X86/MCTargetDesc/X86MCCodeEmitter.cpp
1278

I think it's worthy b/c we uniform the behavior of functions emit*Prefix*, and at the same time, there is almost no extra cost.

craig.topper added inline comments.Feb 7 2023, 9:13 AM
llvm/lib/Target/X86/MCTargetDesc/X86MCCodeEmitter.cpp
1148

The names of these functions is confusing. getKind followed by setKind looks odd. I would naively expect setKind to set the thing that getKind returns. So getKind followed by setKind looks redundant.

skan updated this revision to Diff 495700.Feb 7 2023, 5:59 PM
skan marked an inline comment as done.

Address review comments

llvm/lib/Target/X86/MCTargetDesc/X86MCCodeEmitter.cpp
1148

You're right. I re-designed the interface to setLowerBound, determineOptimalKind.

pengfei added inline comments.Feb 8 2023, 7:24 PM
llvm/lib/Target/X86/MCTargetDesc/X86MCCodeEmitter.cpp
784–786

Format.

866–883

Should be better to move below logic into X86OpcodePrefix ? Then we can simplify the function to:

X86OpcodePrefix Prefix(*Ctx.getRegisterInfo(), TSFlags);
Prefix.emit(OS);
return Prefix.getKind();
871

Should be better to use setEncoding?

skan added inline comments.Feb 8 2023, 7:41 PM
llvm/lib/Target/X86/MCTargetDesc/X86MCCodeEmitter.cpp
784–786

Will do

866–883

We can not b/c we still need to read the operands the instruction before the final emit.
We shouldn't b/c the first principle mentioned in the summary:

"Make code clearer by separating the logic of setting bits from the logic of how a prefix is encoded"

871

I think setLowerBound is more clear than setEncoding here b/c VEX and REX are determined at the last stage. e.g
Prefix.setEncoding(VEX2) here is counterintuitive.

skan updated this revision to Diff 496006.Feb 8 2023, 7:44 PM

Clang format

skan marked 3 inline comments as done.Feb 8 2023, 7:45 PM
skan marked 4 inline comments as done.Feb 8 2023, 8:04 PM
pengfei accepted this revision.Feb 8 2023, 9:23 PM

LGTM. Please wait for some days for other reviewers.

This revision is now accepted and ready to land.Feb 8 2023, 9:23 PM
craig.topper added inline comments.Feb 8 2023, 9:23 PM
llvm/lib/Target/X86/MCTargetDesc/X86MCCodeEmitter.cpp
874

explicit*

craig.topper added inline comments.Feb 8 2023, 9:31 PM
llvm/lib/Target/X86/MCTargetDesc/X86MCCodeEmitter.cpp
140

I'm not sure if it makes sense to have this class understand MRI and MI. I kind of think it should only receive the register encoding.

Especially when you look at funcions like setRR2 that now end up looking up the encoding twice because it nests calls to setR and setR2.

Thinking.... What if we took this further and had an "encodeable instruction" object that contains the prefix fields and the modrm fields that we build by walking the operands and format once. Then we would only have 1 switch on format instead of the 3 we have now. Then we use that to emit the prefix, the opcode, and the modrm byte.

skan updated this revision to Diff 496023.Feb 8 2023, 9:49 PM
skan marked an inline comment as done.

Fix typo and format code

skan added a comment.Feb 8 2023, 10:12 PM

Thinking.... What if we took this further and had an "encodeable instruction" object that contains the prefix fields and the modrm fields that we build by walking the operands and format once. Then we would only have 1 switch on format instead of the 3 we have now. Then we use that to emit the prefix, the opcode, and the modrm byte.

This sounds good! Let me give a try.

llvm/lib/Target/X86/MCTargetDesc/X86MCCodeEmitter.cpp
140

I agree the name X86OpcodePrefix doesn't sound like much to understand MI, we should have a "Helper" suffix.

Encoding twice is not a big issue. We can easily extract a function "getX86RegEncoding" to avoid this.

I am thinking your new proposal "encodeable instruction"

skan updated this revision to Diff 496107.Feb 9 2023, 6:12 AM

Rename class name and avoid duplicated function call

skan added a comment.Feb 9 2023, 6:29 AM

Thinking.... What if we took this further and had an "encodable instruction" object that contains the prefix fields and the modrm fields that we build by walking the operands and format once. Then we would only have 1 switch on format instead of the 3 we have now. Then we use that to emit the prefix, the opcode, and the modrm byte.

This sounds good! Let me give a try.

@craig.topper I think it's doable with some space cost. e.g, we need to reserve some bits in "encodable instruction" to represent the "REG", "MOD", "RM" and "SIB" and It's even true when we only would like to emitPrefix.
The code change for encodable instruction is quite a big and can share some common code of this patch. I suggest we land this first and possibly implement "encodable instruction" based on it.

This revision was landed with ongoing or failed builds.Feb 9 2023, 7:11 PM
This revision was automatically updated to reflect the committed changes.

Hi,

I noticed that if I run lit tests with ubsan built binaries with this patch there are many many tests failing like

../lib/Target/X86/MCTargetDesc/X86MCCodeEmitter.cpp:222:19: runtime error: left shift of negative value -1
skan added a comment.EditedFeb 10 2023, 5:17 AM

Hi,

I noticed that if I run lit tests with ubsan built binaries with this patch there are many many tests failing like

../lib/Target/X86/MCTargetDesc/X86MCCodeEmitter.cpp:222:19: runtime error: left shift of negative value -1

I think it's false positive?

~R << 7

R is an unsigned, so ~R is unsigned too. Although the value of ~R may be 0xffffffff. But I remember the left shift operation on unsigned is always well defined as long as the shift not exceed the size of unsigned.

Hi,

I noticed that if I run lit tests with ubsan built binaries with this patch there are many many tests failing like

../lib/Target/X86/MCTargetDesc/X86MCCodeEmitter.cpp:222:19: runtime error: left shift of negative value -1

I think it's false positive?

~R << 7

R is an unsigned, so ~R is unsigned too. Although the value of ~R may be 0xffffffff. But I remember the left shift operation on unsigned is always well defined as long as the shift not exceed the size of unsigned.

I remember the << operator is defined to shift signed int in C language. There's no relationship with the type of shifted value.

skan added a comment.Feb 10 2023, 6:40 AM

Hi,

I noticed that if I run lit tests with ubsan built binaries with this patch there are many many tests failing like

../lib/Target/X86/MCTargetDesc/X86MCCodeEmitter.cpp:222:19: runtime error: left shift of negative value -1

I think it's false positive?

~R << 7

R is an unsigned, so ~R is unsigned too. Although the value of ~R may be 0xffffffff. But I remember the left shift operation on unsigned is always well defined as long as the shift not exceed the size of unsigned.

I remember the << operator is defined to shift signed int in C language. There's no relationship with the type of shifted value.

I am not an expert in latest C standard. But we are in C++

https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4713.pdf

8.5.7 Shift operators [expr.shift]
1 The shift operators << and >> group left-to-right.
shift-expression:
additive-expression
shift-expression << additive-expression
shift-expression >> additive-expression
The operands shall be of integral or unscoped enumeration type and integral promotions are performed. The
type of the result is that of the promoted left operand. The behavior is undefined if the right operand is
negative, or greater than or equal to the length in bits of the promoted left operand.
2 The value of E1 << E2 is E1 left-shifted E2 bit positions; vacated bits are zero-filled. If E1 has an unsigned
type, the value of the result is E1 × 2
E2, reduced modulo one more than the maximum value representable in
the result type. Otherwise, if E1 has a signed type and non-negative value, and E1 × 2
E2 is representable
in the corresponding unsigned type of the result type, then that value, converted to the result type, is the
resulting value; otherwise, the behavior is undefined.

According to the rule, unsigned does not need to be promoted and the shift operation is still on a unsigned.

Hi,

I noticed that if I run lit tests with ubsan built binaries with this patch there are many many tests failing like

../lib/Target/X86/MCTargetDesc/X86MCCodeEmitter.cpp:222:19: runtime error: left shift of negative value -1

Hi, the LLDB sanitized bots are also failing with this error https://green.lab.llvm.org/green/view/LLDB/job/lldb-cmake-sanitized/3335/console

I think uabelho is right about the promotion. According to the standard: "The operand of ~ shall have integral or unscoped enumeration type; the result is the ones’ complement of its operand. Integral promotions are performed."

skan added a comment.Feb 10 2023, 3:09 PM

I just pushed d37a31cf237cb4f8a18b12c91a9204feca5900ef to fix the usbsan issue

Thank you @craig.topper !