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

Add InstructionEncoder.Switch to enable emitting switch instructions with LabelHandles #40546

Closed
Tracked by #75358
SeeminglyScience opened this issue Aug 7, 2020 · 13 comments · Fixed by #76526
Closed
Tracked by #75358
Assignees
Labels
api-approved API was approved in API review, it can be implemented area-System.Reflection.Metadata
Milestone

Comments

@SeeminglyScience
Copy link

SeeminglyScience commented Aug 7, 2020

Background and Motivation

It doesn't look like there's a way to use InstructionEncoder.DefineLabel's LabelHandles with a switch instruction. I'm mainly asking if there is a way to do it that I'm missing, but if not here's a basic API proposal to enable it.

Proposed API

namespace System.Reflection.Metadata.Ecma335
{
    public readonly struct InstructionEncoder
    {
+       public void Switch(params LabelHandle[] label);
    }
}

Usage Examples

var ie = new InstructionEncoder(new BlobBuilder(), new ControlFlowBuilder());
var case0 = ie.DefineLabel();
var case1 = ie.DefineLabel();
var case2 = ie.DefineLabel();
var afterCases = ie.DefineLabel();

ie.OpCode(ILOpCode.Ldc_i4_0);
ie.Switch(case0, case1, case2);
ie.Branch(ILOpCode.Br_s, afterCases);

ie.MarkLabel(case0);
ie.OpCode(ILOpCode.Ldc_i4_0);
ie.OpCode(ILOpCode.Ret);

ie.MarkLabel(case1);
ie.OpCode(ILOpCode.Ldc_i4_1);
ie.OpCode(ILOpCode.Ret);

ie.MarkLabel(case2);
ie.OpCode(ILOpCode.Ldc_i4_2);
ie.OpCode(ILOpCode.Ret);

ie.MarkLabel(afterCases);
ie.OpCode(ILOpCode.Ldc_i4_s);
ie.CodeBuilder.WriteCompressedSignedInteger(10);
ie.OpCode(ILOpCode.Ret);

new MethodBodyStreamEncoder(new BlobBuilder())
    .AddMethodBody(ie, maxStack: 8, localVariablesSignature: default, MethodBodyAttributes.None);

Alternative Designs

Originally this proposal was based on emitting the label by itself:

namespace System.Reflection.Metadata.Ecma335
{
    public readonly struct InstructionEncoder
    {
+       public void Label(LabelHandle label);
    }
}

This won't work because the target offset is based on the end of the switch instruction, not the offset of the label.

Also for it to work with switch like intended, it would need to only emit int32 offsets. That's not super clear from the name and would generate bad IL if someone were to do OpCode(ILOpCode.Br_s); Label(label);, even if it were based on the label's offset.

Risks

@SeeminglyScience SeeminglyScience added the api-suggestion Early API idea and discussion, it is NOT ready for implementation label Aug 7, 2020
@Dotnet-GitSync-Bot Dotnet-GitSync-Bot added area-Meta untriaged New issue has not been triaged by the area owner labels Aug 7, 2020
@SeeminglyScience SeeminglyScience changed the title Add InstructionEncoder.Label to emit LabelHandle offset for switch instructions Add InstructionEncoder.Switch to enable emitting switch instructions with LabelHandles Aug 9, 2020
@ericstj ericstj added this to the Future milestone Aug 26, 2020
@ericstj ericstj removed the untriaged New issue has not been triaged by the area owner label Aug 26, 2020
@ericstj ericstj modified the milestones: Future, 6.0.0 Aug 26, 2020
@krwq krwq modified the milestones: 6.0.0, 7.0.0 Jul 8, 2021
@krwq krwq moved this from v-Next to Future in Triage POD for Meta, Reflection, etc Jul 15, 2021
@teo-tsirpanis
Copy link
Contributor

I think it should also have an overload for IReadOnlyCollection<LabelHandle>, to allow high-performance apps to reuse a List<LabelHandle>.

@ghost ghost moved this from Future to Needs triage in Triage POD for Meta, Reflection, etc Aug 5, 2021
@ghost ghost added the needs-further-triage Issue has been initially triaged, but needs deeper consideration or reconsideration label Aug 5, 2021
@joperezr joperezr removed the needs-further-triage Issue has been initially triaged, but needs deeper consideration or reconsideration label Aug 9, 2021
@joperezr joperezr moved this from Needs triage to Future in Triage POD for Meta, Reflection, etc Aug 9, 2021
@teo-tsirpanis
Copy link
Contributor

I have refactored the relevant SRM code to easily support emitting switch. Is there anything that prevents the proposal from going forward?

@ghost ghost moved this from Future to Needs triage in Triage POD for Meta, Reflection, etc Dec 6, 2021
@ghost ghost added the needs-further-triage Issue has been initially triaged, but needs deeper consideration or reconsideration label Dec 6, 2021
@buyaa-n
Copy link
Member

buyaa-n commented Dec 8, 2021

@tmat what do you think about this proposal? The main question is there a way to use InstructionEncoder.DefineLabel's LabelHandles with a switch instruction

@August-Alm
Copy link

Any updates? I would really like to see this proposal implemented! (I'd be very thankful if someone could provide a temporary workaround.)

@SeeminglyScience
Copy link
Author

Any updates? I would really like to see this proposal implemented! (I'd be very thankful if someone could provide a temporary workaround.)

This is probably not the temporary workaround you're hoping for, but what I did was basically rip out and modify their flow control builder code. Here's the PR where I did that if you want some ideas SeeminglyScience/ILAssembler#19

@teo-tsirpanis
Copy link
Contributor

@buyaa-n, @tmat can this move forward? And @SeeminglyScience can you also propose an overload that takes an IReadOnlyCollection<LabelHandle>?

@SeeminglyScience
Copy link
Author

And @SeeminglyScience can you also propose an overload that takes an IReadOnlyCollection<LabelHandle>?

I tried to keep the proposal as close to other APIs in the library as possible. If we were adding overloads for performance, I'd rather see ReadOnlySpan<LabelHandle> added (but that also is not present in the API surface today). For now, I'm going to keep the proposal as is.

@teo-tsirpanis
Copy link
Contributor

Yeah, spans would not fit because they are nowhere else used in this library. IReadOnlyCollection would be the next most memory-efficient solution (in a library where memory efficiency is a theme), but let's wait for the API owners' opinion.

@tmat
Copy link
Member

tmat commented Jul 5, 2022

We could add the proposed API that takes LabelHandle[] but a non-allocating version like following would be more aligned with the spirit of the encoder APIs.

public readonly struct SwitchInstructionEncoder
{
   private readonly ControlFlowBuilder _branchBuilder;
   private readonly BlobBuilder _codeBuilder;
   private readonly int _instructionEndOffset;

   internal SwitchInstructionEncoder(ControlFlowBuilder branchBuilder, BlobBuilder codeBuilder, int instructionEndOffset) { ... }

   public void Branch(Label label)
     => _branchBuilder.AddBranch(_instructionEndOffset, label, _codeBuilder);
}

public readonly struct InstructionEncoder
{
   public SwitchInstructionEncoder Switch(int branchCount)
   {
      var branchBuilder = GetBranchBuilder();
      OpCode(ILOpCodes.Switch);      
      CodeBuilder.WriteInt32(branchCount);
      return new SwitchInstructionEncoder(branchBuilder, CodeBuilder, Offset + branchCount * sizeof(uint));
   }
}

@buyaa-n buyaa-n modified the milestones: 7.0.0, 8.0.0 Jul 6, 2022
@buyaa-n buyaa-n added api-ready-for-review API is ready for review, it is NOT ready for implementation and removed api-suggestion Early API idea and discussion, it is NOT ready for implementation labels Jul 6, 2022
@bartonjs
Copy link
Member

bartonjs commented Aug 9, 2022

Video

  • We went with the alternative proposal with SwitchInstructionEncoder instead of the params array, since it felt more consistent with existing paradigms.
namespace System.Reflection.Metadata.Ecma335
{
    public readonly struct SwitchInstructionEncoder
    {
        public void Branch(LabelHandle label);
    }

    public partial struct InstructionEncoder
    {
        public SwitchInstructionEncoder Switch(int branchCount);
    }
}

@bartonjs bartonjs added api-approved API was approved in API review, it can be implemented and removed api-ready-for-review API is ready for review, it is NOT ready for implementation labels Aug 9, 2022
@teo-tsirpanis teo-tsirpanis self-assigned this Aug 9, 2022
@teo-tsirpanis
Copy link
Contributor

I will do it in a couple of months in time for 8.0.0, if someone else wants to do it, feel free to.

@August-Alm
Copy link

The original proposal had a clear usage example but the approved API leaves the intended functionality implicit. Am I correct in understanding the approved proposal as supposed to work in such a way that the originally suggested method is equivalent to the following InstructionEncoder method?

public void SwitchOfLabelHandles(params LabelHandle[] labels)
{
    var switchEncoder = Switch(labels.Length);
    foreach (var label in labels)
    {
        switchEncoder.Branch(label);
    }
}

@teo-tsirpanis
Copy link
Contributor

@August-Alm yes.

@teo-tsirpanis teo-tsirpanis removed the needs-further-triage Issue has been initially triaged, but needs deeper consideration or reconsideration label Aug 15, 2022
@buyaa-n buyaa-n modified the milestones: 8.0.0, Future Sep 26, 2022
@ghost ghost added the in-pr There is an active PR which will close this issue when it is merged label Oct 3, 2022
@steveharter steveharter modified the milestones: Future, 8.0.0 Nov 7, 2022
@ghost ghost removed the in-pr There is an active PR which will close this issue when it is merged label Dec 6, 2022
@ghost ghost locked as resolved and limited conversation to collaborators Jan 5, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
api-approved API was approved in API review, it can be implemented area-System.Reflection.Metadata
Projects
No open projects
Development

Successfully merging a pull request may close this issue.