Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JIT: Add GT_SWIFT_ERROR_RET to represent loading error register upon return #100692

Merged
merged 29 commits into from
Apr 12, 2024

Conversation

amanasifkhalid
Copy link
Member

Follow-up to #100429. When creating return nodes during importation, if the method has a SwiftError* out parameter, the JIT will create a GT_SWIFT_ERROR_RET node before the regular GT_RETURN node to represent loading the SwiftError pseudolocal's value into the error register. We initially keep this node before the GT_RETURN block so we don't have to teach the JIT that it is a new valid terminator for BBJ_RETURN blocks; during lowering, we move this node to the end of the block so the error register won't get trashed after loading the error value. I found this to be an easier approach than trying to replace GT_RETURN with a binary node that holds the regular and error return values.

Old codegen:

; Assembly listing for method ErrorHandlingTests:ConditionallySetErrorTo21(ulong,int) (FullOpts)
; Emitting BLENDED_CODE for X64 with AVX - Unix
; FullOpts code
; optimized code
; rbp based frame
; partially interruptible
; No PGO data
; 0 inlinees with PGO data; 2 single block inlinees; 0 inlinees without PGO data
; Final local variable assignments
;
;* V00 arg0         [V00    ] (  0,  0   )    long  ->  zero-ref    single-def
;  V01 arg1         [V01,T00] (  3,  3   )     int  ->  rbx         single-def
;* V02 loc0         [V02    ] (  0,  0   )  struct ( 8) zero-ref    ld-addr-op <System.Runtime.InteropServices.Swift.SwiftError>
;  V03 tmp0         [V03    ] (  3,  2   )  struct ( 8) [rbp-0x10]  do-not-enreg[XS] must-init addr-exposed "SwiftError pseudolocal" <System.Runtime.InteropServices.Swift.SwiftError>
;# V04 OutArgs      [V04    ] (  1,  1   )  struct ( 0) [rsp+0x00]  do-not-enreg[XS] addr-exposed "OutgoingArgSpace"
;* V05 tmp2         [V05    ] (  0,  0   )  struct ( 8) zero-ref    ld-addr-op "NewObj constructor temp" <System.Runtime.InteropServices.Swift.SwiftError>
;  V06 tmp3         [V06    ] (  3,  3   )  struct (16) [rbp-0x20]  do-not-enreg[XS] must-init addr-exposed "Reverse Pinvoke FrameVar"
;* V07 tmp4         [V07,T01] (  0,  0   )    long  ->  zero-ref    single-def "field V02.<Value>k__BackingField (fldOffset=0x0)" P-INDEP
;* V08 tmp5         [V08,T02] (  0,  0   )    long  ->  zero-ref    single-def "field V05.<Value>k__BackingField (fldOffset=0x0)" P-INDEP
;
; Lcl frame size = 24

G_M3386_IG01:  ;; offset=0x0000
       push     rbp
       push     rbx
       sub      rsp, 24
       lea      rbp, [rsp+0x20]
       vxorps   xmm8, xmm8, xmm8
       vmovdqa  xmmword ptr [rbp-0x20], xmm8
       xor      eax, eax
       mov      qword ptr [rbp-0x10], rax
       mov      ebx, edi
						;; size=29 bbWeight=1 PerfScore 6.58
G_M3386_IG02:  ;; offset=0x001D
       lea      rdi, [rbp-0x20]
       call     CORINFO_HELP_JIT_REVERSE_PINVOKE_ENTER
       test     ebx, ebx
       je       SHORT G_M3386_IG04
						;; size=13 bbWeight=1 PerfScore 2.75
G_M3386_IG03:  ;; offset=0x002A
       xor      rdi, rdi
       mov      qword ptr [rbp-0x10], 21
       jmp      SHORT G_M3386_IG05
						;; size=12 bbWeight=0.50 PerfScore 1.62
G_M3386_IG04:  ;; offset=0x0036
       xor      edi, edi
       mov      qword ptr [rbp-0x10], rdi
						;; size=6 bbWeight=0.50 PerfScore 0.62
G_M3386_IG05:  ;; offset=0x003C
       lea      rdi, [rbp-0x20]
       call     CORINFO_HELP_JIT_REVERSE_PINVOKE_EXIT
       mov      r12, qword ptr [rbp-0x10]
						;; size=13 bbWeight=1 PerfScore 2.50
G_M3386_IG06:  ;; offset=0x0049
       add      rsp, 24
       pop      rbx
       pop      rbp
       ret      
						;; size=7 bbWeight=1 PerfScore 2.25

; Total bytes of code 80, prolog size 27, PerfScore 16.33, instruction count 25, allocated bytes for code 80 (MethodHash=763af2c5) for method ErrorHandlingTests:ConditionallySetErrorTo21(ulong,int) (FullOpts)
; ============================================================

New codegen:

; Assembly listing for method ErrorHandlingTests:ConditionallySetErrorTo21(ulong,int) (FullOpts)
; Emitting BLENDED_CODE for X64 with AVX - Unix
; FullOpts code
; optimized code
; rbp based frame
; partially interruptible
; No PGO data
; 0 inlinees with PGO data; 2 single block inlinees; 0 inlinees without PGO data
; Final local variable assignments
;
;* V00 arg0         [V00    ] (  0,  0   )    long  ->  zero-ref    single-def
;  V01 arg1         [V01,T00] (  3,  3   )     int  ->  rbx         single-def
;* V02 loc0         [V02    ] (  0,  0   )  struct ( 8) zero-ref    ld-addr-op <System.Runtime.InteropServices.Swift.SwiftError>
;  V03 tmp0         [V03    ] (  1,  1   )  struct ( 8) [rbp-0x10]  must-init "SwiftError pseudolocal" <System.Runtime.InteropServices.Swift.SwiftError>
;# V04 OutArgs      [V04    ] (  1,  1   )  struct ( 0) [rsp+0x00]  do-not-enreg[XS] addr-exposed "OutgoingArgSpace"
;* V05 tmp2         [V05    ] (  0,  0   )  struct ( 8) zero-ref    ld-addr-op "NewObj constructor temp" <System.Runtime.InteropServices.Swift.SwiftError>
;  V06 tmp3         [V06    ] (  3,  3   )  struct (16) [rbp-0x20]  do-not-enreg[XS] must-init addr-exposed "Reverse Pinvoke FrameVar"
;* V07 tmp4         [V07    ] (  0,  0   )    long  ->  zero-ref    single-def "field V02.<Value>k__BackingField (fldOffset=0x0)" P-INDEP
;* V08 tmp5         [V08    ] (  0,  0   )    long  ->  zero-ref    "field V03.<Value>k__BackingField (fldOffset=0x0)" P-INDEP
;* V09 tmp6         [V09    ] (  0,  0   )    long  ->  zero-ref    single-def "field V05.<Value>k__BackingField (fldOffset=0x0)" P-INDEP
;
; Lcl frame size = 24

G_M3386_IG01:  ;; offset=0x0000
       push     rbp
       push     rbx
       sub      rsp, 24
       lea      rbp, [rsp+0x20]
       vxorps   xmm8, xmm8, xmm8
       vmovdqa  xmmword ptr [rbp-0x20], xmm8
       xor      eax, eax
       mov      qword ptr [rbp-0x10], rax
       mov      ebx, edi
						;; size=29 bbWeight=1 PerfScore 6.58
G_M3386_IG02:  ;; offset=0x001D
       lea      rdi, [rbp-0x20]
       call     CORINFO_HELP_JIT_REVERSE_PINVOKE_ENTER
       test     ebx, ebx
       je       SHORT G_M3386_IG04
						;; size=13 bbWeight=1 PerfScore 2.75
G_M3386_IG03:  ;; offset=0x002A
       xor      rdi, rdi
       mov      edi, 21
       mov      r12, rdi
       jmp      SHORT G_M3386_IG05
						;; size=12 bbWeight=0.50 PerfScore 1.38
G_M3386_IG04:  ;; offset=0x0036
       xor      edi, edi
       mov      r12, rdi
						;; size=5 bbWeight=0.50 PerfScore 0.25
G_M3386_IG05:  ;; offset=0x003B
       lea      rdi, [rbp-0x20]
       call     CORINFO_HELP_JIT_REVERSE_PINVOKE_EXIT
       nop      
						;; size=10 bbWeight=1 PerfScore 1.75
G_M3386_IG06:  ;; offset=0x0045
       add      rsp, 24
       pop      rbx
       pop      rbp
       ret      
						;; size=7 bbWeight=1 PerfScore 2.25

; Total bytes of code 76, prolog size 27, PerfScore 14.96, instruction count 26, allocated bytes for code 76 (MethodHash=763af2c5) for method ErrorHandlingTests:ConditionallySetErrorTo21(ulong,int) (FullOpts)
; ============================================================


@dotnet-issue-labeler dotnet-issue-labeler bot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Apr 5, 2024
@jakobbotsch
Copy link
Member

jakobbotsch commented Apr 5, 2024

We initially keep this node before the GT_RETURN block so we don't have to teach the JIT that it is a new valid terminator for BBJ_RETURN blocks; during lowering, we move this node to the end of the block so the error register won't get trashed after loading the error value. I found this to be an easier approach than trying to replace GT_RETURN with a binary node that holds the regular and error return values.

I'm not sure it's quite that simple due transforms within the JIT that assume knowledge about GT_RETURN and its semantics, and base transformations on this knowledge. This representation makes it error prone to write transforms around GT_RETURN since you aren't forced to handle this new terminator node. It seems intrinsic to me that the JIT has to be taught that this is a new kind of terminator node because of this.
For example, what happens in a function that has a switch with 50 returns in it? I would expect us to do return merging and end up with IR that doesn't handle the error correctly with resulting potential silent bad codegen.

@amanasifkhalid
Copy link
Member Author

For example, what happens in a function that has a switch with 50 returns in it? I would expect us to do return merging and end up with IR that doesn't handle the error correctly with resulting potential silent bad codegen.

I see. In that case, let me try substituting GT_SWIFT_ERROR_RET for GT_RETURN then. In the meantime, let's see how much TP we're trading to get this codegen improvement...

@jakobbotsch
Copy link
Member

FWIW I think it's very likely we'll end up similarly having to expand the JIT's set of known and handled terminator nodes as part of async2. It has a similar situation with an async continuation that is returned in a different register than usual. I'm pretty sure the prototype has similar issues around return merging as the one I described above.

@amanasifkhalid
Copy link
Member Author

FWIW I think it's very likely we'll end up similarly having to expand the JIT's set of known and handled terminator nodes as part of async2.

This might be too radical, but what if we expanded GT_RETURN to have an optional second argument to handle these scenarios, instead of introducing new nodes for them?

@jakobbotsch
Copy link
Member

I'm not sure whether it makes things much simpler -- you still have to audit the interesting uses of GT_RETURN on whether they need special handling of the potential second operand. It also becomes a bit easier to forget to handle the second operand. I like the explicitness of just having new types of nodes. In my experience this kind of explicitness pays off in the long run.

A more uniform representation would be to introduce the ability for the JIT to create new class layouts on the fly. In fact we can already do that for layouts without GC pointers, which would be sufficient for Swift (see typGetBlkLayout). Then you would consider a Swift function with error returns to return a tuple (x, y) where y is the error.
We wouldn't be able to handle something like this efficiently today, but it's sort of on my list in #93105 (the items around supporting FIELD_LIST for returns).

@jakobbotsch
Copy link
Member

jakobbotsch commented Apr 5, 2024

I think the uniform representation above would also generalize to GT_CALL with swift error returns where we would similarly create a new layout for the return that defines the error too, allowing us to remove GT_SWIFT_ERROR. But again, this requires a lot of work to handle efficiently -- it's essentially the "allow struct fields of physically promoted call returns to stay in registers" of #93105.
We would also need to generalize the ABI information carried in ReturnTypeDesc so that the frontend can set this up and have the backend automatically handle the details around getting the values out of the corresponding registers (for GT_CALL) or putting them into the right registers (for GT_RETURN).

It's probably a lot of work, but seems like it would be the right long term direction.

@amanasifkhalid
Copy link
Member Author

I have a binop implementation of GT_SWIFT_ERROR_RET working locally. There are some quirks that I have to clean up and document before pushing, but the codegen looks slightly better:

; Assembly listing for method ErrorHandlingTests:ConditionallySetErrorTo21(ulong,int) (FullOpts)
; Emitting BLENDED_CODE for X64 with AVX - Unix
; FullOpts code
; optimized code
; rbp based frame
; partially interruptible
; No PGO data
; 0 inlinees with PGO data; 2 single block inlinees; 0 inlinees without PGO data
; Final local variable assignments
;
;* V00 arg0         [V00    ] (  0,  0   )    long  ->  zero-ref    single-def
;  V01 arg1         [V01,T00] (  3,  3   )     int  ->  rbx         single-def
;* V02 loc0         [V02    ] (  0,  0   )  struct ( 8) zero-ref    ld-addr-op <System.Runtime.InteropServices.Swift.SwiftError>
;  V03 tmp0         [V03    ] (  1,  1   )  struct ( 8) [rbp-0x10]  must-init "SwiftError pseudolocal" <System.Runtime.InteropServices.Swift.SwiftError>
;# V04 OutArgs      [V04    ] (  1,  1   )  struct ( 0) [rsp+0x00]  do-not-enreg[XS] addr-exposed "OutgoingArgSpace"
;* V05 tmp2         [V05    ] (  0,  0   )  struct ( 8) zero-ref    ld-addr-op "NewObj constructor temp" <System.Runtime.InteropServices.Swift.SwiftError>
;  V06 tmp3         [V06    ] (  3,  3   )  struct (16) [rbp-0x20]  do-not-enreg[XS] must-init addr-exposed "Reverse Pinvoke FrameVar"
;* V07 tmp4         [V07,T02] (  0,  0   )    long  ->  zero-ref    single-def "field V02.<Value>k__BackingField (fldOffset=0x0)" P-INDEP
;  V08 tmp5         [V08,T01] (  3,  2   )    long  ->  rbx         "field V03.<Value>k__BackingField (fldOffset=0x0)" P-INDEP
;* V09 tmp6         [V09,T03] (  0,  0   )    long  ->  zero-ref    single-def "field V05.<Value>k__BackingField (fldOffset=0x0)" P-INDEP
;
; Lcl frame size = 24

G_M3386_IG01:  ;; offset=0x0000
       push     rbp
       push     rbx
       sub      rsp, 24
       lea      rbp, [rsp+0x20]
       vxorps   xmm8, xmm8, xmm8
       vmovdqa  xmmword ptr [rbp-0x20], xmm8
       xor      eax, eax
       mov      qword ptr [rbp-0x10], rax
       mov      ebx, edi
						;; size=29 bbWeight=1 PerfScore 6.58
G_M3386_IG02:  ;; offset=0x001D
       lea      rdi, [rbp-0x20]
       call     CORINFO_HELP_JIT_REVERSE_PINVOKE_ENTER
       test     ebx, ebx
       je       SHORT G_M3386_IG04
						;; size=13 bbWeight=1 PerfScore 2.75
G_M3386_IG03:  ;; offset=0x002A
       xor      rdi, rdi
       mov      ebx, 21
       jmp      SHORT G_M3386_IG05
						;; size=9 bbWeight=0.50 PerfScore 1.25
G_M3386_IG04:  ;; offset=0x0033
       xor      ebx, ebx
						;; size=2 bbWeight=0.50 PerfScore 0.12
G_M3386_IG05:  ;; offset=0x0035
       lea      rdi, [rbp-0x20]
       call     CORINFO_HELP_JIT_REVERSE_PINVOKE_EXIT
       mov      r12, rbx
						;; size=12 bbWeight=1 PerfScore 1.75
G_M3386_IG06:  ;; offset=0x0041
       add      rsp, 24
       pop      rbx
       pop      rbp
       ret      
						;; size=7 bbWeight=1 PerfScore 2.25

; Total bytes of code 72, prolog size 27, PerfScore 14.71, instruction count 24, allocated bytes for code 72 (MethodHash=763af2c5) for method ErrorHandlingTests:ConditionallySetErrorTo21(ulong,int) (FullOpts)
; ============================================================

@amanasifkhalid
Copy link
Member Author

@jakobbotsch PTAL. GT_SWIFT_ERROR_RET now takes the error value as its first operand, and the normal return value as its second operand. I wanted to make the normal return value the first operand to match GT_RETURN, but if the method has a void return type, then it would be possible for the first operand of GT_SWIFT_ERROR_RET to be null -- modeling this in the JIT isn't elegant, so I think it's better to flip the operands, and swap them whenever we can handle GT_RETURN and GT_SWIFT_ERROR_RET nodes simultaneously to reduce code duplication.

@jakobbotsch
Copy link
Member

I'll take a closer look tomorrow, but I'm curious why the null op1 didn't work out given that we have some prior art with GenTree::NullOp1Legal. Wasn't that sufficient?

Might be a bit cleaner to introduce something like

GenTree* GetReturnedValue(GenTreeOp* op)
{
  assert(op->OperIs(GT_RETURN, GT_RETFILT, GT_SWIFT_ERROR_RET));
  return op->OperIs(GT_SWIFT_ERROR_RET) ? op->gtGetOp2() : op->gtGetOp1();
}

Mutating GenTree at that point seems a bit iffy...

@amanasifkhalid
Copy link
Member Author

I'll take a closer look tomorrow, but I'm curious why the null op1 didn't work out given that we have some prior art with GenTree::NullOp1Legal. Wasn't that sufficient?

I initially tried that, but when handling simple operators in a bunch of places, we either assume binops have a first operand, or we assume the lack of a first operand implies the second operand is null as well (like in Compiler::gtSetEvalOrder). NullOp1Legal handles some binops already, but we only call it when handling unops. Perhaps I moved on from trying to match the operand order of GT_RETURN too quickly, though I think your proposed GetReturnedValue would work nicely; right now, I think we'd only need it in a few places.

@amanasifkhalid
Copy link
Member Author

/azp run runtime-coreclr jitstress, runtime-coreclr jitstressregs, runtime-coreclr jitstress2-jitstressregs

Copy link

Azure Pipelines successfully started running 3 pipeline(s).

@@ -290,7 +290,8 @@ GTNODE(END_LFIN , GenTreeVal ,0,0,GTK_LEAF|GTK_NOVALUE) // End l
// Swift interop-specific nodes:
//-----------------------------------------------------------------------------

GTNODE(SWIFT_ERROR , GenTree ,0,0,GTK_LEAF) // Error register value post-Swift call
GTNODE(SWIFT_ERROR , GenTree ,0,0,GTK_LEAF) // Error register value post-Swift call
GTNODE(SWIFT_ERROR_RET , GenTreeOp ,0,1,GTK_BINOP|GTK_NOVALUE) // Returns normal return value, and SwiftError pseudolocal's value in REG_SWIFT_ERROR
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice with a comment that op1 has the error and op2 has the normal return value (or null if method returns void)

Comment on lines 10887 to 10899
// If this method has a SwiftError* out parameter, we need to set the error register upon returning.
// By placing the GT_SWIFT_ERROR_RET node before the GT_RETURN node,
// we don't have to teach the JIT that GT_SWIFT_ERROR_RET is a valid terminator for BBJ_RETURN blocks.
// During lowering, we will move the GT_SWIFT_ERROR_RET to the end of the block
// so the error register won't get trashed when returning the normal value.
if (lvaSwiftErrorArg != BAD_VAR_NUM)
{
assert(info.compCallConv == CorInfoCallConvExtension::Swift);
assert(lvaSwiftErrorLocal != BAD_VAR_NUM);
GenTree* const swiftErrorNode = gtNewLclFldNode(lvaSwiftErrorLocal, TYP_I_IMPL, 0);
op1 = new (this, GT_SWIFT_ERROR_RET)
GenTreeOp(GT_SWIFT_ERROR_RET, op1->TypeGet(), swiftErrorNode, op1->gtGetOp1());
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment is outdated now, right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use op1->SetOper(GT_SWIFT_ERROR_RET) instead here to avoid the new node allocation. Of course that requires some shuffling of the operands, so just allocating a new node is also ok with me, if you like that better feel free to ignore this.

Comment on lines 4640 to 4648
GenTree* Lowering::LowerSwiftErrorRet(GenTree* swiftErrorRet)
{
assert(swiftErrorRet->OperIs(GT_SWIFT_ERROR_RET));
GenTree* const nextNode = swiftErrorRet->gtNext;
LIR::Range& blockRange = BlockRange();
blockRange.Remove(swiftErrorRet);
blockRange.InsertAtEnd(swiftErrorRet);
return nextNode;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume this isn't needed anymore?

@@ -286,8 +295,9 @@ bool OptIfConversionDsc::IfConvertCheckStmts(BasicBlock* fromBlock, IfConvertOpe
}

case GT_RETURN:
case GT_SWIFT_ERROR_RET:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think if-conversion supports GT_RETURN to transform

JTRUE(EQ(x, 0)) -> BB01, BB02

BB01:
RETURN(0)

BB02:
RETURN(1)

into

RETURN(SELECT(EQ(x, 0), 0, 1))

I don't see us being able to support this transform for GT_SWIFT_ERROR_RET, so I think we can't add this support here.

Comment on lines 14119 to 14121
const DebugInfo& di = lastStmt->GetDebugInfo();
GenTree* swiftErrorStore =
gtNewTempStore(genReturnErrorLocal, ret->gtGetOp1(), CHECK_SPILL_NONE, nullptr, di, block);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const DebugInfo& di = lastStmt->GetDebugInfo();
GenTree* swiftErrorStore =
gtNewTempStore(genReturnErrorLocal, ret->gtGetOp1(), CHECK_SPILL_NONE, nullptr, di, block);
GenTree* swiftErrorStore =
gtNewTempStore(genReturnErrorLocal, ret->gtGetOp1());

(For pAfterStmt == nullptr these last parameters aren't relevant)

Comment on lines 11700 to 11711
case GT_SWIFT_ERROR_RET:
if (tree->gtGetOp2() != nullptr)
{
tree->gtVNPair = vnStore->VNPWithExc(vnStore->VNPForVoid(),
vnStore->VNPExceptionSet(tree->gtGetOp2()->gtVNPair));
}
else
{
tree->gtVNPair = vnStore->VNPForVoid();
}
break;

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should have the exceptions from the error operand propagated too.

Comment on lines 2597 to 2604
fgWalkResult PreOrderVisit(GenTree** use, GenTree* user)
{
if ((*use)->OperIs(GT_LCL_VAR) && ((*use)->AsLclVarCommon()->GetLclNum() == m_compiler->lvaSwiftErrorArg))
{
*use = m_compiler->gtNewLclVarAddrNode(m_compiler->lvaSwiftErrorLocal, genActualType(*use));
}

return fgWalkResult::WALK_CONTINUE;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we BADCODE here if we see a use of the error parameter that isn't a GT_LCL_VAR? E.g. any store or taking the address of it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That seems reasonable to me. Do you know if we have any plans upstream to regulate the usage of the error parameter? I imagine we wouldn't want the JIT to do these checks alone.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure. cc @jkoritzinsky @kotlarmilos. Does it seem appropriate to you to put restrictions on what the IL can do with the SwiftError* parameter? Specifically we're interested in disallowing stores and disallowing taking its address.

{
if (!(*use)->OperIs(GT_LCL_VAR))
{
BADCODE("Expected GT_LCL_VAR of SwiftError* parameter, got other GenTreeLclVarCommon node");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the message should be phrased in terms of IL, not IR constructs

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would something like "Found invalid use of SwiftError* parameter" be too vague?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That sounds good to me

@amanasifkhalid
Copy link
Member Author

/azp run runtime-coreclr jitstress, runtime-coreclr jitstressregs, runtime-coreclr jitstress2-jitstressregs

Copy link

Azure Pipelines successfully started running 3 pipeline(s).

{
assert(genReturnErrorLocal == BAD_VAR_NUM);
genReturnErrorLocal = lvaGrabTemp(true DEBUGARG("Single return block SwiftError value"));
lvaGetDesc(genReturnErrorLocal)->lvType = genActualType(TYP_I_IMPL);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
lvaGetDesc(genReturnErrorLocal)->lvType = genActualType(TYP_I_IMPL);
lvaGetDesc(genReturnErrorLocal)->lvType = TYP_I_IMPL;

Comment on lines 143 to 152
#ifdef SWIFT_SUPPORT
if (m_comp->lvaSwiftErrorArg != BAD_VAR_NUM)
{
m_mainOper = GT_SWIFT_ERROR_RET;
}
else
#endif // SWIFT_SUPPORT
{
m_mainOper = GT_RETURN;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these changes necessary?
It looks like IfConvertCheckStmts will fail naturally for GT_SWIFT_ERROR_RET, so perhaps no changes in if-conversion are necessary?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, we don't check m_mainOper until after we call IfConvertCheckStmts, and we bail if the terminator node is a GT_SWIFT_ERROR_RET, so it should be safe to undo these changes.

Comment on lines 523 to 527
case GT_SWIFT_ERROR_RET:
LowerNode(node->gtGetOp1());

FALLTHROUGH;
case GT_RETURN:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
case GT_SWIFT_ERROR_RET:
LowerNode(node->gtGetOp1());
FALLTHROUGH;
case GT_RETURN:
case GT_SWIFT_ERROR_RET:
case GT_RETURN:

Otherwise we invoke lowering of op1 twice.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LowerRet should probably be changed to take GenTreeOp* instead.


// Propagate side effect flags
tree->SetAllEffectsFlags(tree->AsOp()->gtGetOp1());
tree->SetAllEffectsFlags(retVal);

return tree;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This case needs to invoke morph on op1 as well for GT_SWIFT_ERROR_RET (I am not sure why the code returns here in the first place, it probably ought to just fall through after inserting the cast)

Copy link
Member

@jakobbotsch jakobbotsch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great to me now. Thanks for your diligence in implementing this!

@amanasifkhalid
Copy link
Member Author

This looks great to me now. Thanks for your diligence in implementing this!

Thank you for all the reviews! In case you're curious, here's the updated codegen for ConditionallySetErrorTo21:

; Assembly listing for method ErrorHandlingTests:ConditionallySetErrorTo21(ulong,int) (FullOpts)
; Emitting BLENDED_CODE for X64 with AVX - Unix
; FullOpts code
; optimized code
; rsp based frame
; partially interruptible
; No PGO data
; 0 inlinees with PGO data; 2 single block inlinees; 0 inlinees without PGO data
; Final local variable assignments
;
;* V00 arg0         [V00    ] (  0,  0   )    long  ->  zero-ref    single-def
;  V01 arg1         [V01,T00] (  3,  3   )     int  ->  r12         single-def
;  V02 tmp0         [V02    ] (  1,  1   )  struct ( 8) [rsp+0x10]  "SwiftError pseudolocal" <System.Runtime.InteropServices.Swift.SwiftError>
;# V03 OutArgs      [V03    ] (  1,  1   )  struct ( 0) [rsp+0x00]  do-not-enreg[XS] addr-exposed "OutgoingArgSpace"
;* V04 tmp2         [V04    ] (  0,  0   )  struct ( 8) zero-ref    ld-addr-op "NewObj constructor temp" <System.Runtime.InteropServices.Swift.SwiftError>
;* V05 tmp3         [V05    ] (  0,  0   )  struct ( 8) zero-ref    ld-addr-op "NewObj constructor temp" <System.Runtime.InteropServices.Swift.SwiftError>
;  V06 tmp4         [V06    ] (  3,  3   )  struct (16) [rsp+0x00]  do-not-enreg[XS] addr-exposed "Reverse Pinvoke FrameVar"
;  V07 tmp5         [V07,T01] (  2,  4   )    long  ->  r12         "Single return block SwiftError value"
;* V08 tmp6         [V08    ] (  0,  0   )    long  ->  zero-ref    "field V02.<Value>k__BackingField (fldOffset=0x0)" P-INDEP
;* V09 tmp7         [V09    ] (  0,  0   )    long  ->  zero-ref    "field V04.<Value>k__BackingField (fldOffset=0x0)" P-INDEP
;* V10 tmp8         [V10    ] (  0,  0   )    long  ->  zero-ref    "field V05.<Value>k__BackingField (fldOffset=0x0)" P-INDEP
;
; Lcl frame size = 24

G_M3386_IG01:  ;; offset=0x0000
       sub      rsp, 24
       mov      r12d, edi
                                                ;; size=7 bbWeight=1 PerfScore 0.50
G_M3386_IG02:  ;; offset=0x0007
       lea      rdi, [rsp]
       call     CORINFO_HELP_JIT_REVERSE_PINVOKE_ENTER
       xor      edi, edi
       mov      eax, 21
       test     r12d, r12d
       mov      r12, rax
       cmove    r12, rdi
       lea      rdi, [rsp]
       call     CORINFO_HELP_JIT_REVERSE_PINVOKE_EXIT
       nop
                                                ;; size=36 bbWeight=1 PerfScore 4.50
G_M3386_IG03:  ;; offset=0x002B
       add      rsp, 24
       ret
                                                ;; size=5 bbWeight=1 PerfScore 1.25

; Total bytes of code 48, prolog size 4, PerfScore 6.25, instruction count 14, allocated bytes for code 48 (MethodHash=763af2c5) for method ErrorHandlingTests:ConditionallySetErrorTo21(ulong,int) (FullOpts)
; ============================================================

Quite the improvement.

@amanasifkhalid amanasifkhalid merged commit ec76487 into dotnet:main Apr 12, 2024
108 of 110 checks passed
@amanasifkhalid amanasifkhalid deleted the swift-error-reg-ret branch April 12, 2024 19:26
matouskozak pushed a commit to matouskozak/runtime that referenced this pull request Apr 30, 2024
…return (dotnet#100692)

Follow-up to dotnet#100429. If a method has a `SwiftError*` out parameter, a new phase -- `fgAddSwiftErrorReturns` -- converts all `GT_RETURN` nodes into `GT_SWIFT_ERROR_RET` nodes; this new node type is a binop that takes the error value as its first operand, and the normal return value (if there is one) as its second operand. The error value is loaded into the Swift error register upon returning.
@github-actions github-actions bot locked and limited conversation to collaborators May 13, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants