| | | 1 | | // Licensed to the .NET Foundation under one or more agreements. |
| | | 2 | | // The .NET Foundation licenses this file to you under the MIT license. |
| | | 3 | | |
| | | 4 | | namespace System.Reflection.Metadata.Ecma335 |
| | | 5 | | { |
| | | 6 | | /// <summary> |
| | | 7 | | /// Encodes method body stream. |
| | | 8 | | /// </summary> |
| | | 9 | | public readonly struct MethodBodyStreamEncoder |
| | | 10 | | { |
| | 0 | 11 | | public BlobBuilder Builder { get; } |
| | | 12 | | |
| | | 13 | | public MethodBodyStreamEncoder(BlobBuilder builder) |
| | 0 | 14 | | { |
| | 0 | 15 | | ArgumentNullException.ThrowIfNull(builder); |
| | | 16 | | |
| | | 17 | | // Fat methods are 4-byte aligned. We calculate the alignment relative to the start of the ILStream. |
| | | 18 | | // |
| | | 19 | | // See ECMA-335 paragraph 25.4.5, Method data section: |
| | | 20 | | // "At the next 4-byte boundary following the method body can be extra method data sections." |
| | 0 | 21 | | if ((builder.Count % 4) != 0) |
| | 0 | 22 | | { |
| | 0 | 23 | | throw new ArgumentException(SR.BuilderMustAligned, nameof(builder)); |
| | | 24 | | } |
| | | 25 | | |
| | 0 | 26 | | Builder = builder; |
| | 0 | 27 | | } |
| | | 28 | | |
| | | 29 | | /// <summary> |
| | | 30 | | /// Encodes a method body and adds it to the method body stream. |
| | | 31 | | /// </summary> |
| | | 32 | | /// <param name="codeSize">Number of bytes to be reserved for instructions.</param> |
| | | 33 | | /// <param name="maxStack">Max stack.</param> |
| | | 34 | | /// <param name="exceptionRegionCount">Number of exception regions.</param> |
| | | 35 | | /// <param name="hasSmallExceptionRegions">True if the exception regions should be encoded in 'small' format.</p |
| | | 36 | | /// <param name="localVariablesSignature">Local variables signature handle.</param> |
| | | 37 | | /// <param name="attributes">Attributes.</param> |
| | | 38 | | /// <returns>The offset of the encoded body within the method body stream.</returns> |
| | | 39 | | /// <exception cref="ArgumentOutOfRangeException"> |
| | | 40 | | /// <paramref name="codeSize"/>, <paramref name="exceptionRegionCount"/>, or <paramref name="maxStack"/> is out |
| | | 41 | | /// </exception> |
| | | 42 | | public MethodBody AddMethodBody( |
| | | 43 | | int codeSize, |
| | | 44 | | int maxStack, |
| | | 45 | | int exceptionRegionCount, |
| | | 46 | | bool hasSmallExceptionRegions, |
| | | 47 | | StandaloneSignatureHandle localVariablesSignature, |
| | | 48 | | MethodBodyAttributes attributes) |
| | 0 | 49 | | => AddMethodBody(codeSize, maxStack, exceptionRegionCount, hasSmallExceptionRegions, localVariablesSignature |
| | | 50 | | |
| | | 51 | | /// <summary> |
| | | 52 | | /// Encodes a method body and adds it to the method body stream. |
| | | 53 | | /// </summary> |
| | | 54 | | /// <param name="codeSize">Number of bytes to be reserved for instructions.</param> |
| | | 55 | | /// <param name="maxStack">Max stack.</param> |
| | | 56 | | /// <param name="exceptionRegionCount">Number of exception regions.</param> |
| | | 57 | | /// <param name="hasSmallExceptionRegions">True if the exception regions should be encoded in 'small' format.</p |
| | | 58 | | /// <param name="localVariablesSignature">Local variables signature handle.</param> |
| | | 59 | | /// <param name="attributes">Attributes.</param> |
| | | 60 | | /// <param name="hasDynamicStackAllocation">True if the method allocates from dynamic local memory pool (<c>loca |
| | | 61 | | /// <returns>The offset of the encoded body within the method body stream.</returns> |
| | | 62 | | /// <exception cref="ArgumentOutOfRangeException"> |
| | | 63 | | /// <paramref name="codeSize"/>, <paramref name="exceptionRegionCount"/>, or <paramref name="maxStack"/> is out |
| | | 64 | | /// </exception> |
| | | 65 | | public MethodBody AddMethodBody( |
| | | 66 | | int codeSize, |
| | | 67 | | int maxStack = 8, |
| | | 68 | | int exceptionRegionCount = 0, |
| | | 69 | | bool hasSmallExceptionRegions = true, |
| | | 70 | | StandaloneSignatureHandle localVariablesSignature = default, |
| | | 71 | | MethodBodyAttributes attributes = MethodBodyAttributes.InitLocals, |
| | | 72 | | bool hasDynamicStackAllocation = false) |
| | 0 | 73 | | { |
| | 0 | 74 | | if (codeSize < 0) |
| | 0 | 75 | | { |
| | 0 | 76 | | Throw.ArgumentOutOfRange(nameof(codeSize)); |
| | | 77 | | } |
| | | 78 | | |
| | 0 | 79 | | if (unchecked((uint)maxStack) > ushort.MaxValue) |
| | 0 | 80 | | { |
| | 0 | 81 | | Throw.ArgumentOutOfRange(nameof(maxStack)); |
| | | 82 | | } |
| | | 83 | | |
| | 0 | 84 | | if (!ExceptionRegionEncoder.IsExceptionRegionCountInBounds(exceptionRegionCount)) |
| | 0 | 85 | | { |
| | 0 | 86 | | Throw.ArgumentOutOfRange(nameof(exceptionRegionCount)); |
| | | 87 | | } |
| | | 88 | | |
| | 0 | 89 | | int bodyOffset = SerializeHeader(codeSize, (ushort)maxStack, exceptionRegionCount, attributes, localVariable |
| | 0 | 90 | | var instructions = Builder.ReserveBytes(codeSize); |
| | | 91 | | |
| | 0 | 92 | | var regionEncoder = (exceptionRegionCount > 0) ? |
| | 0 | 93 | | ExceptionRegionEncoder.SerializeTableHeader(Builder, exceptionRegionCount, hasSmallExceptionRegions) : d |
| | | 94 | | |
| | 0 | 95 | | return new MethodBody(bodyOffset, instructions, regionEncoder); |
| | 0 | 96 | | } |
| | | 97 | | |
| | | 98 | | public readonly struct MethodBody |
| | | 99 | | { |
| | | 100 | | /// <summary> |
| | | 101 | | /// Offset of the encoded method body in method body stream. |
| | | 102 | | /// </summary> |
| | 0 | 103 | | public int Offset { get; } |
| | | 104 | | |
| | | 105 | | /// <summary> |
| | | 106 | | /// Blob reserved for instructions. |
| | | 107 | | /// </summary> |
| | 0 | 108 | | public Blob Instructions { get; } |
| | | 109 | | |
| | | 110 | | /// <summary> |
| | | 111 | | /// Use to encode exception regions to the method body. |
| | | 112 | | /// </summary> |
| | 0 | 113 | | public ExceptionRegionEncoder ExceptionRegions { get; } |
| | | 114 | | |
| | | 115 | | internal MethodBody(int bodyOffset, Blob instructions, ExceptionRegionEncoder exceptionRegions) |
| | 0 | 116 | | { |
| | 0 | 117 | | Offset = bodyOffset; |
| | 0 | 118 | | Instructions = instructions; |
| | 0 | 119 | | ExceptionRegions = exceptionRegions; |
| | 0 | 120 | | } |
| | | 121 | | } |
| | | 122 | | |
| | | 123 | | /// <summary> |
| | | 124 | | /// Encodes a method body and adds it to the method body stream. |
| | | 125 | | /// </summary> |
| | | 126 | | /// <param name="instructionEncoder">Instruction encoder.</param> |
| | | 127 | | /// <param name="maxStack">Max stack.</param> |
| | | 128 | | /// <param name="localVariablesSignature">Local variables signature handle.</param> |
| | | 129 | | /// <param name="attributes">Attributes.</param> |
| | | 130 | | /// <returns>The offset of the encoded body within the method body stream.</returns> |
| | | 131 | | /// <exception cref="ArgumentNullException"><paramref name="instructionEncoder"/> has default value.</exception> |
| | | 132 | | /// <exception cref="ArgumentOutOfRangeException"><paramref name="maxStack"/> is out of range [0, <see cref="ush |
| | | 133 | | /// <exception cref="InvalidOperationException"> |
| | | 134 | | /// A label targeted by a branch in the instruction stream has not been marked, |
| | | 135 | | /// or the distance between a branch instruction and the target label doesn't fit the size of the instruction op |
| | | 136 | | /// </exception> |
| | | 137 | | public int AddMethodBody( |
| | | 138 | | InstructionEncoder instructionEncoder, |
| | | 139 | | int maxStack, |
| | | 140 | | StandaloneSignatureHandle localVariablesSignature, |
| | | 141 | | MethodBodyAttributes attributes) |
| | 0 | 142 | | => AddMethodBody(instructionEncoder, maxStack, localVariablesSignature, attributes, hasDynamicStackAllocatio |
| | | 143 | | |
| | | 144 | | /// <summary> |
| | | 145 | | /// Encodes a method body and adds it to the method body stream. |
| | | 146 | | /// </summary> |
| | | 147 | | /// <param name="instructionEncoder">Instruction encoder.</param> |
| | | 148 | | /// <param name="maxStack">Max stack.</param> |
| | | 149 | | /// <param name="localVariablesSignature">Local variables signature handle.</param> |
| | | 150 | | /// <param name="attributes">Attributes.</param> |
| | | 151 | | /// <param name="hasDynamicStackAllocation">True if the method allocates from dynamic local memory pool (the IL |
| | | 152 | | /// </param> |
| | | 153 | | /// <returns>The offset of the encoded body within the method body stream.</returns> |
| | | 154 | | /// <exception cref="ArgumentNullException"><paramref name="instructionEncoder"/> has default value.</exception> |
| | | 155 | | /// <exception cref="ArgumentOutOfRangeException"><paramref name="maxStack"/> is out of range [0, <see cref="ush |
| | | 156 | | /// <exception cref="InvalidOperationException"> |
| | | 157 | | /// A label targeted by a branch in the instruction stream has not been marked, |
| | | 158 | | /// or the distance between a branch instruction and the target label doesn't fit the size of the instruction op |
| | | 159 | | /// </exception> |
| | | 160 | | public int AddMethodBody( |
| | | 161 | | InstructionEncoder instructionEncoder, |
| | | 162 | | int maxStack = 8, |
| | | 163 | | StandaloneSignatureHandle localVariablesSignature = default, |
| | | 164 | | MethodBodyAttributes attributes = MethodBodyAttributes.InitLocals, |
| | | 165 | | bool hasDynamicStackAllocation = false) |
| | 0 | 166 | | { |
| | 0 | 167 | | if (unchecked((uint)maxStack) > ushort.MaxValue) |
| | 0 | 168 | | { |
| | 0 | 169 | | Throw.ArgumentOutOfRange(nameof(maxStack)); |
| | | 170 | | } |
| | | 171 | | |
| | | 172 | | // The branch fixup code expects the operands of branch instructions in the code builder to be contiguous. |
| | | 173 | | // That's true when we emit thru InstructionEncoder. Taking it as a parameter instead of separate |
| | | 174 | | // code and flow builder parameters ensures they match each other. |
| | 0 | 175 | | var codeBuilder = instructionEncoder.CodeBuilder; |
| | 0 | 176 | | var flowBuilder = instructionEncoder.ControlFlowBuilder; |
| | | 177 | | |
| | 0 | 178 | | if (codeBuilder == null) |
| | 0 | 179 | | { |
| | 0 | 180 | | Throw.ArgumentNull(nameof(instructionEncoder)); |
| | | 181 | | } |
| | | 182 | | |
| | 0 | 183 | | int exceptionRegionCount = flowBuilder?.ExceptionHandlerCount ?? 0; |
| | 0 | 184 | | if (!ExceptionRegionEncoder.IsExceptionRegionCountInBounds(exceptionRegionCount)) |
| | 0 | 185 | | { |
| | 0 | 186 | | Throw.ArgumentOutOfRange(nameof(instructionEncoder), SR.TooManyExceptionRegions); |
| | | 187 | | } |
| | | 188 | | |
| | 0 | 189 | | flowBuilder?.ValidateNotInSwitch(); |
| | | 190 | | |
| | | 191 | | // Note (see also https://github.com/dotnet/runtime/issues/24948) |
| | | 192 | | // |
| | | 193 | | // We could potentially automatically determine whether a tiny method with no variables and InitLocals flag |
| | | 194 | | // has localloc instruction and thus needs a fat header. We could parse the IL stored in codeBuilder. |
| | | 195 | | // However, it would unnecessarily slow down emit of virtually all tiny methods, which do not use localloc |
| | | 196 | | // and would only address uninitialized memory issues in very rare scenarios when the pointer returned by |
| | | 197 | | // localloc is not stored in a local variable but passed to a method call or stored in a field. |
| | | 198 | | // |
| | | 199 | | // Since emitting code with localloc is already a pretty advanced scenario that emits unsafe code |
| | | 200 | | // that can be potentially incorrect in many other ways we decide that it's not worth the complexity |
| | | 201 | | // and a perf regression to do so. Instead we rely on the caller to let us know if there is a localloc |
| | | 202 | | // in the code they emitted. |
| | | 203 | | |
| | 0 | 204 | | int bodyOffset = SerializeHeader(codeBuilder.Count, (ushort)maxStack, exceptionRegionCount, attributes, loca |
| | | 205 | | |
| | 0 | 206 | | if (flowBuilder?.BranchCount > 0) |
| | 0 | 207 | | { |
| | 0 | 208 | | flowBuilder.CopyCodeAndFixupBranches(codeBuilder, Builder); |
| | 0 | 209 | | } |
| | | 210 | | else |
| | 0 | 211 | | { |
| | 0 | 212 | | codeBuilder.WriteContentTo(Builder); |
| | 0 | 213 | | } |
| | | 214 | | |
| | 0 | 215 | | flowBuilder?.SerializeExceptionTable(Builder); |
| | | 216 | | |
| | 0 | 217 | | return bodyOffset; |
| | 0 | 218 | | } |
| | | 219 | | |
| | | 220 | | private int SerializeHeader( |
| | | 221 | | int codeSize, |
| | | 222 | | ushort maxStack, |
| | | 223 | | int exceptionRegionCount, |
| | | 224 | | MethodBodyAttributes attributes, |
| | | 225 | | StandaloneSignatureHandle localVariablesSignature, |
| | | 226 | | bool hasDynamicStackAllocation) |
| | 0 | 227 | | { |
| | | 228 | | const int TinyFormat = 2; |
| | | 229 | | const int FatFormat = 3; |
| | | 230 | | const int MoreSections = 8; |
| | | 231 | | const byte InitLocals = 0x10; |
| | | 232 | | |
| | 0 | 233 | | bool initLocals = (attributes & MethodBodyAttributes.InitLocals) != 0; |
| | | 234 | | |
| | 0 | 235 | | bool isTiny = codeSize < 64 && |
| | 0 | 236 | | maxStack <= 8 && |
| | 0 | 237 | | localVariablesSignature.IsNil && (!hasDynamicStackAllocation || !initLocals) && |
| | 0 | 238 | | exceptionRegionCount == 0; |
| | | 239 | | |
| | | 240 | | int offset; |
| | 0 | 241 | | if (isTiny) |
| | 0 | 242 | | { |
| | 0 | 243 | | offset = Builder.Count; |
| | 0 | 244 | | Builder.WriteByte((byte)((codeSize << 2) | TinyFormat)); |
| | 0 | 245 | | } |
| | | 246 | | else |
| | 0 | 247 | | { |
| | 0 | 248 | | Builder.Align(4); |
| | | 249 | | |
| | 0 | 250 | | offset = Builder.Count; |
| | | 251 | | |
| | 0 | 252 | | ushort flags = (3 << 12) | FatFormat; |
| | 0 | 253 | | if (exceptionRegionCount > 0) |
| | 0 | 254 | | { |
| | 0 | 255 | | flags |= MoreSections; |
| | 0 | 256 | | } |
| | | 257 | | |
| | 0 | 258 | | if (initLocals) |
| | 0 | 259 | | { |
| | 0 | 260 | | flags |= InitLocals; |
| | 0 | 261 | | } |
| | | 262 | | |
| | 0 | 263 | | Builder.WriteUInt16((ushort)((int)attributes | flags)); |
| | 0 | 264 | | Builder.WriteUInt16(maxStack); |
| | 0 | 265 | | Builder.WriteInt32(codeSize); |
| | 0 | 266 | | Builder.WriteInt32(localVariablesSignature.IsNil ? 0 : MetadataTokens.GetToken(localVariablesSignature)) |
| | 0 | 267 | | } |
| | | 268 | | |
| | 0 | 269 | | return offset; |
| | 0 | 270 | | } |
| | | 271 | | } |
| | | 272 | | } |
| | | 273 | | |