| File: parent\parent\Common\TypeSystem\IL\Stubs\ILEmitter.cs | Web Access |
| Project: ILCompiler.TypeSystem.csproj (ILCompiler.TypeSystem) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Buffers.Binary; using System.Collections.Generic; using System.Runtime.CompilerServices; using Internal.TypeSystem; using Debug = System.Diagnostics.Debug; namespace Internal.IL.Stubs { public class ILCodeStream { private const int StartOffsetNotSet = -1; private struct LabelAndOffset { public readonly ILCodeLabel Label; public readonly int Offset; public LabelAndOffset(ILCodeLabel label, int offset) { Label = label; Offset = offset; } } internal byte[] _instructions; internal int _length; internal int _startOffsetForLinking; internal ArrayBuilder<ILSequencePoint> _sequencePoints; private ArrayBuilder<LabelAndOffset> _offsetsNeedingPatching; private ILEmitter _emitter; internal ILCodeStream(ILEmitter emitter) { _instructions = Array.Empty<byte>(); _startOffsetForLinking = StartOffsetNotSet; _emitter = emitter; } internal int RelativeToAbsoluteOffset(int relativeOffset) { Debug.Assert(_startOffsetForLinking != StartOffsetNotSet); return _startOffsetForLinking + relativeOffset; } private void EmitByte(byte b) { if (_length == _instructions.Length) Grow(); _instructions[_length++] = b; } private void EmitUInt16(ushort value) { if (_length + sizeof(ushort) > _instructions.Length) Grow(); BinaryPrimitives.WriteUInt16LittleEndian(_instructions.AsSpan(_length, sizeof(ushort)), value); _length += sizeof(ushort); } private void EmitUInt32(int value) { if (_length + sizeof(int) > _instructions.Length) Grow(); BinaryPrimitives.WriteInt32LittleEndian(_instructions.AsSpan(_length, sizeof(int)), value); _length += sizeof(int); } [MethodImpl(MethodImplOptions.NoInlining)] private void Grow() { Array.Resize(ref _instructions, 2 * _instructions.Length + 10); } public void Emit(ILOpcode opcode) { if ((int)opcode > 0x100) EmitByte((byte)ILOpcode.prefix1); EmitByte((byte)opcode); } public void Emit(ILOpcode opcode, ILToken token) { Emit(opcode); EmitUInt32((int)token); } public void EmitLdc(int value) { if (-1 <= value && value <= 8) { Emit((ILOpcode)(ILOpcode.ldc_i4_0 + value)); } else if (value == (sbyte)value) { Emit(ILOpcode.ldc_i4_s); EmitByte((byte)value); } else { Emit(ILOpcode.ldc_i4); EmitUInt32(value); } } public void EmitLdArg(int index) { Debug.Assert(index >= 0); if (index < 4) { Emit((ILOpcode)(ILOpcode.ldarg_0 + index)); } else { Emit(ILOpcode.ldarg); EmitUInt16((ushort)index); } } public void EmitLdArga(int index) { Debug.Assert(index >= 0); if (index < 0x100) { Emit(ILOpcode.ldarga_s); EmitByte((byte)index); } else { Emit(ILOpcode.ldarga); EmitUInt16((ushort)index); } } public void EmitLdLoc(ILLocalVariable variable) { int index = (int)variable; if (index < 4) { Emit((ILOpcode)(ILOpcode.ldloc_0 + index)); } else if (index < 0x100) { Emit(ILOpcode.ldloc_s); EmitByte((byte)index); } else { Emit(ILOpcode.ldloc); EmitUInt16((ushort)index); } } public void EmitLdLoca(ILLocalVariable variable) { int index = (int)variable; if (index < 0x100) { Emit(ILOpcode.ldloca_s); EmitByte((byte)index); } else { Emit(ILOpcode.ldloca); EmitUInt16((ushort)index); } } public void EmitStLoc(ILLocalVariable variable) { int index = (int)variable; if (index < 4) { Emit((ILOpcode)(ILOpcode.stloc_0 + index)); } else if (index < 0x100) { Emit(ILOpcode.stloc_s); EmitByte((byte)index); } else { Emit(ILOpcode.stloc); EmitUInt16((ushort)index); } } public void Emit(ILOpcode opcode, ILCodeLabel label) { Debug.Assert(opcode == ILOpcode.br || opcode == ILOpcode.brfalse || opcode == ILOpcode.brtrue || opcode == ILOpcode.beq || opcode == ILOpcode.bge || opcode == ILOpcode.bgt || opcode == ILOpcode.ble || opcode == ILOpcode.blt || opcode == ILOpcode.bne_un || opcode == ILOpcode.bge_un || opcode == ILOpcode.bgt_un || opcode == ILOpcode.ble_un || opcode == ILOpcode.blt_un || opcode == ILOpcode.leave); Emit(opcode); _offsetsNeedingPatching.Add(new LabelAndOffset(label, _length)); EmitUInt32(4); } public void EmitSwitch(ILCodeLabel[] labels) { Emit(ILOpcode.switch_); EmitUInt32(labels.Length); int remainingBytes = labels.Length * 4; foreach (var label in labels) { _offsetsNeedingPatching.Add(new LabelAndOffset(label, _length)); EmitUInt32(remainingBytes); remainingBytes -= 4; } } public void EmitUnaligned() { Emit(ILOpcode.unaligned); EmitByte(1); } public void EmitLdInd(TypeDesc type) { switch (type.UnderlyingType.Category) { case TypeFlags.SByte: Emit(ILOpcode.ldind_i1); break; case TypeFlags.Byte: case TypeFlags.Boolean: Emit(ILOpcode.ldind_u1); break; case TypeFlags.Int16: Emit(ILOpcode.ldind_i2); break; case TypeFlags.Char: case TypeFlags.UInt16: Emit(ILOpcode.ldind_u2); break; case TypeFlags.UInt32: Emit(ILOpcode.ldind_u4); break; case TypeFlags.Int32: Emit(ILOpcode.ldind_i4); break; case TypeFlags.UInt64: case TypeFlags.Int64: Emit(ILOpcode.ldind_i8); break; case TypeFlags.Single: Emit(ILOpcode.ldind_r4); break; case TypeFlags.Double: Emit(ILOpcode.ldind_r8); break; case TypeFlags.IntPtr: case TypeFlags.UIntPtr: case TypeFlags.Pointer: case TypeFlags.FunctionPointer: Emit(ILOpcode.ldind_i); break; case TypeFlags.Array: case TypeFlags.SzArray: case TypeFlags.Class: case TypeFlags.Interface: Emit(ILOpcode.ldind_ref); break; case TypeFlags.ValueType: case TypeFlags.Nullable: case TypeFlags.SignatureMethodVariable: case TypeFlags.SignatureTypeVariable: Emit(ILOpcode.ldobj, _emitter.NewToken(type)); break; default: Debug.Fail("Unexpected TypeDesc category"); break; } } public void EmitStInd(TypeDesc type) { switch (type.UnderlyingType.Category) { case TypeFlags.Byte: case TypeFlags.SByte: case TypeFlags.Boolean: Emit(ILOpcode.stind_i1); break; case TypeFlags.Char: case TypeFlags.UInt16: case TypeFlags.Int16: Emit(ILOpcode.stind_i2); break; case TypeFlags.UInt32: case TypeFlags.Int32: Emit(ILOpcode.stind_i4); break; case TypeFlags.UInt64: case TypeFlags.Int64: Emit(ILOpcode.stind_i8); break; case TypeFlags.Single: Emit(ILOpcode.stind_r4); break; case TypeFlags.Double: Emit(ILOpcode.stind_r8); break; case TypeFlags.IntPtr: case TypeFlags.UIntPtr: case TypeFlags.Pointer: case TypeFlags.FunctionPointer: Emit(ILOpcode.stind_i); break; case TypeFlags.Array: case TypeFlags.SzArray: case TypeFlags.Class: case TypeFlags.Interface: Emit(ILOpcode.stind_ref); break; case TypeFlags.ValueType: case TypeFlags.Nullable: Emit(ILOpcode.stobj, _emitter.NewToken(type)); break; default: Debug.Fail("Unexpected TypeDesc category"); break; } } public void EmitStElem(TypeDesc type) { switch (type.UnderlyingType.Category) { case TypeFlags.Byte: case TypeFlags.SByte: case TypeFlags.Boolean: Emit(ILOpcode.stelem_i1); break; case TypeFlags.Char: case TypeFlags.UInt16: case TypeFlags.Int16: Emit(ILOpcode.stelem_i2); break; case TypeFlags.UInt32: case TypeFlags.Int32: Emit(ILOpcode.stelem_i4); break; case TypeFlags.UInt64: case TypeFlags.Int64: Emit(ILOpcode.stelem_i8); break; case TypeFlags.Single: Emit(ILOpcode.stelem_r4); break; case TypeFlags.Double: Emit(ILOpcode.stelem_r8); break; case TypeFlags.IntPtr: case TypeFlags.UIntPtr: case TypeFlags.Pointer: case TypeFlags.FunctionPointer: Emit(ILOpcode.stelem_i); break; case TypeFlags.Array: case TypeFlags.SzArray: case TypeFlags.Class: case TypeFlags.Interface: Emit(ILOpcode.stelem_ref); break; case TypeFlags.ValueType: case TypeFlags.Nullable: Emit(ILOpcode.stelem, _emitter.NewToken(type)); break; default: Debug.Fail("Unexpected TypeDesc category"); break; } } public void EmitLdElem(TypeDesc type) { switch (type.UnderlyingType.Category) { case TypeFlags.Byte: case TypeFlags.SByte: case TypeFlags.Boolean: Emit(ILOpcode.ldelem_i1); break; case TypeFlags.Char: case TypeFlags.UInt16: case TypeFlags.Int16: Emit(ILOpcode.ldelem_i2); break; case TypeFlags.UInt32: case TypeFlags.Int32: Emit(ILOpcode.ldelem_i4); break; case TypeFlags.UInt64: case TypeFlags.Int64: Emit(ILOpcode.ldelem_i8); break; case TypeFlags.Single: Emit(ILOpcode.ldelem_r4); break; case TypeFlags.Double: Emit(ILOpcode.ldelem_r8); break; case TypeFlags.IntPtr: case TypeFlags.UIntPtr: case TypeFlags.Pointer: case TypeFlags.FunctionPointer: Emit(ILOpcode.ldelem_i); break; case TypeFlags.Array: case TypeFlags.SzArray: case TypeFlags.Class: case TypeFlags.Interface: Emit(ILOpcode.ldelem_ref); break; case TypeFlags.ValueType: case TypeFlags.Nullable: Emit(ILOpcode.ldelem, _emitter.NewToken(type)); break; default: Debug.Fail("Unexpected TypeDesc category"); break; } } public void EmitLabel(ILCodeLabel label) { label.Place(this, _length); } public void BeginTry(ILExceptionRegionBuilder builder) { Debug.Assert(builder._beginTryStream == null); builder._beginTryStream = this; builder._beginTryOffset = _length; } public void EndTry(ILExceptionRegionBuilder builder) { Debug.Assert(builder._endTryStream == null); builder._endTryStream = this; builder._endTryOffset = _length; } public void BeginHandler(ILExceptionRegionBuilder builder) { Debug.Assert(builder._beginHandlerStream == null); builder._beginHandlerStream = this; builder._beginHandlerOffset = _length; } public void EndHandler(ILExceptionRegionBuilder builder) { Debug.Assert(builder._endHandlerStream == null); builder._endHandlerStream = this; builder._endHandlerOffset = _length; } internal void PatchLabels() { for (int i = 0; i < _offsetsNeedingPatching.Count; i++) { LabelAndOffset patch = _offsetsNeedingPatching[i]; Debug.Assert(patch.Label.IsPlaced); Debug.Assert(_startOffsetForLinking != StartOffsetNotSet); Span<byte> offsetSpan = _instructions.AsSpan(patch.Offset, sizeof(int)); int delta = BinaryPrimitives.ReadInt32LittleEndian(offsetSpan); int value = patch.Label.AbsoluteOffset - _startOffsetForLinking - patch.Offset - delta; BinaryPrimitives.WriteInt32LittleEndian(offsetSpan, value); } } public void DefineSequencePoint(string document, int lineNumber) { // Last sequence point defined for this offset wins. if (_sequencePoints.Count > 0 && _sequencePoints[_sequencePoints.Count - 1].Offset == _length) { _sequencePoints[_sequencePoints.Count - 1] = new ILSequencePoint(_length, document, lineNumber); } else { _sequencePoints.Add(new ILSequencePoint(_length, document, lineNumber)); } } } public class ILExceptionRegionBuilder { internal ILCodeStream _beginTryStream; internal int _beginTryOffset; internal ILCodeStream _endTryStream; internal int _endTryOffset; internal ILCodeStream _beginHandlerStream; internal int _beginHandlerOffset; internal ILCodeStream _endHandlerStream; internal int _endHandlerOffset; internal ILExceptionRegionKind _exceptionRegionKind; internal TypeDesc _catchExceptionType; internal ILExceptionRegionBuilder(ILExceptionRegionKind exceptionRegionKind, TypeDesc catchExceptionType = null) { _exceptionRegionKind = exceptionRegionKind; _catchExceptionType = catchExceptionType; Debug.Assert((exceptionRegionKind == ILExceptionRegionKind.Catch && catchExceptionType != null) || (exceptionRegionKind != ILExceptionRegionKind.Catch && catchExceptionType == null)); } internal int TryOffset => _beginTryStream.RelativeToAbsoluteOffset(_beginTryOffset); internal int TryLength => _endTryStream.RelativeToAbsoluteOffset(_endTryOffset) - TryOffset; internal int HandlerOffset => _beginHandlerStream.RelativeToAbsoluteOffset(_beginHandlerOffset); internal int HandlerLength => _endHandlerStream.RelativeToAbsoluteOffset(_endHandlerOffset) - HandlerOffset; internal bool IsDefined => _beginTryStream != null && _endTryStream != null && _beginHandlerStream != null && _endHandlerStream != null; } /// <summary> /// Represent a token. Use one of the overloads of <see cref="ILEmitter.NewToken"/> /// to create a new token. /// </summary> public enum ILToken { } /// <summary> /// Represents a local variable. Use <see cref="ILEmitter.NewLocal"/> to create a new local variable. /// </summary> public enum ILLocalVariable { } public class ILStubMethodIL : MethodIL { private readonly byte[] _ilBytes; private readonly LocalVariableDefinition[] _locals; private readonly object[] _tokens; private readonly MethodDesc _method; private readonly ILExceptionRegion[] _exceptionRegions; private readonly MethodDebugInformation _debugInformation; private const int MaxStackNotSet = -1; private int _maxStack; private bool _stubILHasGeneratedTokens; public ILStubMethodIL(MethodDesc owningMethod, byte[] ilBytes, LocalVariableDefinition[] locals, object[] tokens, ILExceptionRegion[] exceptionRegions = null, MethodDebugInformation debugInfo = null, bool stubILHasGeneratedTokens = false) { _ilBytes = ilBytes; _locals = locals; _tokens = tokens; _method = owningMethod; _stubILHasGeneratedTokens = stubILHasGeneratedTokens; _maxStack = MaxStackNotSet; exceptionRegions ??= Array.Empty<ILExceptionRegion>(); _exceptionRegions = exceptionRegions; debugInfo ??= MethodDebugInformation.None; _debugInformation = debugInfo; } public ILStubMethodIL(ILStubMethodIL methodIL) { _ilBytes = methodIL._ilBytes; _locals = methodIL._locals; _tokens = methodIL._tokens; _method = methodIL._method; _stubILHasGeneratedTokens = methodIL._stubILHasGeneratedTokens; _debugInformation = methodIL._debugInformation; _exceptionRegions = methodIL._exceptionRegions; _maxStack = methodIL._maxStack; } public override MethodDesc OwningMethod { get { return _method; } } public bool StubILHasGeneratedTokens => _stubILHasGeneratedTokens; public override byte[] GetILBytes() { return _ilBytes; } public override MethodDebugInformation GetDebugInfo() { return _debugInformation; } public override int MaxStack { get { if (_maxStack == MaxStackNotSet) _maxStack = this.ComputeMaxStack(); return _maxStack; } } public override ILExceptionRegion[] GetExceptionRegions() { return _exceptionRegions; } public override bool IsInitLocals { get { return true; } } public override LocalVariableDefinition[] GetLocals() { return _locals; } public override object GetObject(int token, NotFoundBehavior notFoundBehavior) { return _tokens[(token & 0xFFFFFF) - 1]; } } public class ILCodeLabel { private ILCodeStream _codeStream; private int _offsetWithinCodeStream; internal bool IsPlaced { get { return _codeStream != null; } } internal int AbsoluteOffset { get { Debug.Assert(IsPlaced); return _codeStream.RelativeToAbsoluteOffset(_offsetWithinCodeStream); } } internal ILCodeLabel() { } internal void Place(ILCodeStream codeStream, int offsetWithinCodeStream) { Debug.Assert(!IsPlaced); _codeStream = codeStream; _offsetWithinCodeStream = offsetWithinCodeStream; } } public class ILEmitter { private ArrayBuilder<ILCodeStream> _codeStreams; private ArrayBuilder<LocalVariableDefinition> _locals; private ArrayBuilder<object> _tokens; private ArrayBuilder<ILExceptionRegionBuilder> _exceptionRegions; private bool _hasGeneratedTokens; public ILEmitter() { } public ILCodeStream NewCodeStream() { ILCodeStream stream = new ILCodeStream(this); _codeStreams.Add(stream); return stream; } public bool SetHasGeneratedTokens() { return _hasGeneratedTokens = true; } private ILToken NewToken(object value, int tokenType) { Debug.Assert(value != null); _tokens.Add(value); return (ILToken)(_tokens.Count | tokenType); } public ILToken NewToken(TypeDesc value) { return NewToken(value, 0x01000000); // mdtTypeRef } public ILToken NewToken(MethodDesc value) { return NewToken(value, 0x0a000000); // mdtMemberRef } public ILToken NewToken(FieldDesc value) { return NewToken(value, 0x0a000000); // mdtMemberRef } public ILToken NewToken(string value) { return NewToken(value, 0x70000000); // mdtString } public ILToken NewToken(MethodSignature value) { return NewToken(value, 0x11000000); // mdtSignature } public ILLocalVariable NewLocal(TypeDesc localType, bool isPinned = false) { int index = _locals.Count; _locals.Add(new LocalVariableDefinition(localType, isPinned)); return (ILLocalVariable)index; } public ILCodeLabel NewCodeLabel() { var newLabel = new ILCodeLabel(); return newLabel; } public ILExceptionRegionBuilder NewCatchRegion(TypeDesc exceptionType) { var region = new ILExceptionRegionBuilder(ILExceptionRegionKind.Catch, exceptionType); _exceptionRegions.Add(region); return region; } public ILExceptionRegionBuilder NewFinallyRegion() { var region = new ILExceptionRegionBuilder(ILExceptionRegionKind.Finally); _exceptionRegions.Add(region); return region; } public MethodIL Link(MethodDesc owningMethod) { int totalLength = 0; int numSequencePoints = 0; for (int i = 0; i < _codeStreams.Count; i++) { ILCodeStream ilCodeStream = _codeStreams[i]; ilCodeStream._startOffsetForLinking = totalLength; totalLength += ilCodeStream._length; numSequencePoints += ilCodeStream._sequencePoints.Count; } byte[] ilInstructions = new byte[totalLength]; int copiedLength = 0; for (int i = 0; i < _codeStreams.Count; i++) { ILCodeStream ilCodeStream = _codeStreams[i]; ilCodeStream.PatchLabels(); Array.Copy(ilCodeStream._instructions, 0, ilInstructions, copiedLength, ilCodeStream._length); copiedLength += ilCodeStream._length; } MethodDebugInformation debugInfo = null; if (numSequencePoints > 0) { ILSequencePoint[] sequencePoints = new ILSequencePoint[numSequencePoints]; int copiedSequencePointLength = 0; for (int codeStreamIndex = 0; codeStreamIndex < _codeStreams.Count; codeStreamIndex++) { ILCodeStream ilCodeStream = _codeStreams[codeStreamIndex]; for (int sequencePointIndex = 0; sequencePointIndex < ilCodeStream._sequencePoints.Count; sequencePointIndex++) { ILSequencePoint sequencePoint = ilCodeStream._sequencePoints[sequencePointIndex]; sequencePoints[copiedSequencePointLength] = new ILSequencePoint( ilCodeStream._startOffsetForLinking + sequencePoint.Offset, sequencePoint.Document, sequencePoint.LineNumber); copiedSequencePointLength++; } } debugInfo = new EmittedMethodDebugInformation(sequencePoints); } ILExceptionRegion[] exceptionRegions = null; int numberOfExceptionRegions = _exceptionRegions.Count; if (numberOfExceptionRegions > 0) { exceptionRegions = new ILExceptionRegion[numberOfExceptionRegions]; for (int i = 0; i < _exceptionRegions.Count; i++) { ILExceptionRegionBuilder region = _exceptionRegions[i]; Debug.Assert(region.IsDefined); int exceptionTypeToken = (region._catchExceptionType != null) ? (int)NewToken(region._catchExceptionType) : 0; exceptionRegions[i] = new ILExceptionRegion(region._exceptionRegionKind, region.TryOffset, region.TryLength, region.HandlerOffset, region.HandlerLength, classToken: exceptionTypeToken, filterOffset: 0); } // Sort exception regions so that innermost (most nested) regions come first // as this is required by the spec. // Innermost regions have higher TryOffset and smaller TryLength. Array.Sort(exceptionRegions, (a, b) => { int offsetComparison = b.TryOffset.CompareTo(a.TryOffset); if (offsetComparison != 0) return offsetComparison; return a.TryLength.CompareTo(b.TryLength); }); } var result = new ILStubMethodIL(owningMethod, ilInstructions, _locals.ToArray(), _tokens.ToArray(), exceptionRegions, debugInfo, _hasGeneratedTokens); result.CheckStackBalance(); return result; } private sealed class EmittedMethodDebugInformation : MethodDebugInformation { private readonly ILSequencePoint[] _sequencePoints; public EmittedMethodDebugInformation(ILSequencePoint[] sequencePoints) { _sequencePoints = sequencePoints; } public override IEnumerable<ILSequencePoint> GetSequencePoints() { return _sequencePoints; } } } public abstract partial class ILStubMethod : MethodDesc { public abstract MethodIL EmitIL(); public override bool HasCustomAttribute(string attributeNamespace, string attributeName) { return false; } } public abstract partial class SpecializableILStubMethod : ILStubMethod { public abstract MethodIL EmitIL(MethodDesc specializedMethod); public override bool HasCustomAttribute(string attributeNamespace, string attributeName) { return false; } } }