From 9a2893a1468488d47bc93176b63883de225fe0d7 Mon Sep 17 00:00:00 2001 From: Yoshi Askharoun Date: Thu, 8 Feb 2024 13:32:44 -0600 Subject: [PATCH] Add type exports to disassembler --- libs/UIX.Asm/Disassembler.cs | 128 +++++++++++++++++++++++++----- libs/UIX.Asm/Lexer.Directives.cs | 17 +++- libs/UIX.Asm/Lexer.cs | 2 + libs/UIX.Asm/Models/Directives.cs | 29 +++++-- 4 files changed, 145 insertions(+), 31 deletions(-) diff --git a/libs/UIX.Asm/Disassembler.cs b/libs/UIX.Asm/Disassembler.cs index 7f4e41f..3c3bde1 100644 --- a/libs/UIX.Asm/Disassembler.cs +++ b/libs/UIX.Asm/Disassembler.cs @@ -3,56 +3,118 @@ using Microsoft.Iris.Asm.Models; using Microsoft.Iris.Markup; using System; using System.Collections.Generic; +using System.Linq; namespace Microsoft.Iris.Asm; public class Disassembler { private readonly MarkupLoadResult _loadResult; + private readonly Dictionary _importedUris; + private readonly Dictionary> _offsetLabelMap = new(); private Disassembler(MarkupLoadResult loadResult) { _loadResult = loadResult; + _importedUris = new() + { + [_loadResult.Uri] = "me", + ["http://schemas.microsoft.com/2007/uix"] = null + }; } public static Disassembler Load(MarkupLoadResult loadResult) => new(loadResult); + public IEnumerable GetExports() + { + foreach (var typeSchema in _loadResult.ExportTable) + { + var labelPrefix = typeSchema.Name; + var baseName = GetQualifiedName(typeSchema.Base); + + if (typeSchema is not MarkupTypeSchema markupTypeSchema) + throw new Exception($"Disassembler failed to disassemble export {typeSchema}, '{typeSchema.GetType()}' is not supported."); + + yield return new ExportDirective(labelPrefix, markupTypeSchema.ListenerCount, markupTypeSchema.Base.Name); + + var propOffset = markupTypeSchema.InitializePropertiesOffset; + InsertLabel(propOffset, ExportDirective.GetInitializePropertiesLabel(labelPrefix)); + + var loclOffset = markupTypeSchema.InitializeLocalsInputOffset; + InsertLabel(loclOffset, ExportDirective.GetInitializeLocalsInputLabel(labelPrefix)); + + var contOffset = markupTypeSchema.InitializeContentOffset; + InsertLabel(contOffset, ExportDirective.GetInitializeContentLabel(labelPrefix)); + + if (markupTypeSchema.InitialEvaluateOffsets != null) + { + for (int i = 0; i < markupTypeSchema.InitialEvaluateOffsets.Length; i++) + { + uint offset = markupTypeSchema.InitialEvaluateOffsets[i]; + var labelName = $"{ExportDirective.GetInitializeContentLabel(labelPrefix)}{i:N}"; + + InsertLabel(offset, labelName); + } + } + + if (markupTypeSchema.FinalEvaluateOffsets != null) + { + for (int i = 0; i < markupTypeSchema.FinalEvaluateOffsets.Length; i++) + { + uint offset = markupTypeSchema.FinalEvaluateOffsets[i]; + var labelName = $"{ExportDirective.GetFinalEvaluateOffsetsLabelPrefix(labelPrefix)}{i:N}"; + + InsertLabel(offset, labelName); + } + + } + + if (markupTypeSchema.RefreshGroupOffsets != null) + { + for (int i = 0; i < markupTypeSchema.RefreshGroupOffsets.Length; i++) + { + uint offset = markupTypeSchema.RefreshGroupOffsets[i]; + var labelName = $"{ExportDirective.GetRefreshGroupOffsetsLabelPrefix(labelPrefix)}{i:N}"; + InsertLabel(offset, labelName); + } + } + } + } + public IEnumerable GetImports() { - // Keep track of what has already been imported. Skip self and default UIX namespace. - Dictionary importedUris = new() { - [_loadResult.Uri] = "me", - ["http://schemas.microsoft.com/2007/uix"] = null - }; + // Ues _importedUris to keep track of what has already been imported. + // Skip self and default UIX namespace. foreach (var typeImport in _loadResult.ImportTables.TypeImports) { var uri = typeImport.Owner.Uri; - if (!importedUris.TryGetValue(uri, out var namespacePrefix)) + if (!_importedUris.TryGetValue(uri, out var namespacePrefix)) { namespacePrefix = uri; - var schemeLength = uri.IndexOf("://"); - if (schemeLength > 0) - { - var scheme = uri[..schemeLength]; - if (scheme == "assembly") + var schemeLength = uri.IndexOf("://"); + if (schemeLength > 0) { - // Assume the URI represents a C# namespace + var scheme = uri[..schemeLength]; + if (scheme == "assembly") + { + // Assume the URI represents a C# namespace namespacePrefix = uri.Split('.', '/', '\\', '!')[^1]; - // Remove the extra assembly info - uri = uri.Split(',')[0]; - } - else - { - // Assume the URI represents a file, - // skip the extension + // Remove the extra assembly info + uri = uri.Split(',')[0]; + } + else + { + // Assume the URI represents a file, + // skip the extension namespacePrefix = uri.Split('.', '/', '\\', '!')[^2]; } } namespacePrefix = namespacePrefix.Camelize(); - importedUris.Add(uri, namespacePrefix); + _importedUris.Add(uri, namespacePrefix); + yield return new NamespaceImport(uri, namespacePrefix); } @@ -69,6 +131,10 @@ public class Disassembler while (reader.CurrentOffset < reader.Size) { + if (_offsetLabelMap.TryGetValue(reader.CurrentOffset, out var labels)) + foreach (var label in labels) + yield return label; + var opCode = (OpCode)reader.ReadByte(); switch (opCode) @@ -183,10 +249,28 @@ public class Disassembler _loadResult.Load(LoadPass.Full); _loadResult.Load(LoadPass.Done); - List imports = new(GetImports()); + List directives = GetImports().Cast() + .Concat(GetExports()) + .ToList(); List body = new(GetBody()); - Program asmProgram = new(imports, body); + Program asmProgram = new(directives, body); return asmProgram.ToString(); } + + private string GetQualifiedName(TypeSchema schema) + { + _importedUris.TryGetValue(schema.Owner.Uri, out string prefix); + + return prefix != null + ? $"{prefix}:{schema.Name}" + : schema.Name; + } + + private void InsertLabel(uint offset, string labelName) + { + if (!_offsetLabelMap.TryGetValue(offset, out var labels)) + labels = _offsetLabelMap[offset] = new(1); + labels.Add(new(labelName)); + } } diff --git a/libs/UIX.Asm/Lexer.Directives.cs b/libs/UIX.Asm/Lexer.Directives.cs index 620be1c..e1baf0d 100644 --- a/libs/UIX.Asm/Lexer.Directives.cs +++ b/libs/UIX.Asm/Lexer.Directives.cs @@ -53,7 +53,22 @@ partial class Lexer if (!labelPrefixResult.WasSuccessful) return Result.Failure(input, "Invalid export directive", ["Expected prefix of labels to export"]); - directive = new ExportDirective(labelPrefixResult.Value) + var listenerCountResult = WholeNumber.Token()(input); + input = listenerCountResult.Remainder; + if (!listenerCountResult.WasSuccessful) + return Result.Failure(input, "Invalid export directive", ["Expected listener count"]); + + if (!uint.TryParse(listenerCountResult.Value, out var listenerCount)) + return Result.Failure(input, "Invalid export directive", ["Expected export listener count to be an unsigned integer"]); + + var baseTypeNameResult = AlphanumericText.Token()(input); + input = baseTypeNameResult.Remainder; + if (!baseTypeNameResult.WasSuccessful) + return Result.Failure(input, "Invalid export directive", ["Expected base type name"]); + + var labelPrefix = labelPrefixResult.Value; + var baseTypeName = baseTypeNameResult.Value; + directive = new ExportDirective(labelPrefix, listenerCount, baseTypeName) { Line = line, Column = column, diff --git a/libs/UIX.Asm/Lexer.cs b/libs/UIX.Asm/Lexer.cs index bd6a994..8c0a137 100644 --- a/libs/UIX.Asm/Lexer.cs +++ b/libs/UIX.Asm/Lexer.cs @@ -10,6 +10,8 @@ public static partial class Lexer public static readonly Parser WordText = Parse.Letter.AtLeastOnce().Text(); + public static readonly Parser WholeNumber = Parse.Digit.AtLeastOnce().Text(); + public static readonly Parser Uri = Parse.LetterOrDigit.Or(Parse.Chars(":/!._-")).AtLeastOnce().Text(); public static readonly Parser StatementEnd = Parse.Char(';').Return(";").Or(Parse.LineTerminator); diff --git a/libs/UIX.Asm/Models/Directives.cs b/libs/UIX.Asm/Models/Directives.cs index 8b9780e..027aeaa 100644 --- a/libs/UIX.Asm/Models/Directives.cs +++ b/libs/UIX.Asm/Models/Directives.cs @@ -14,20 +14,33 @@ public record SectionDirective : Directive, IBodyItem public record ExportDirective : Directive { - public ExportDirective(string labelPrefix) : base("export") + public ExportDirective(string labelPrefix, uint listenerCount, string baseTypeName) : base("export") { LabelPrefix = labelPrefix; + ListenerCount = listenerCount; + BaseTypeName = baseTypeName; } public string LabelPrefix { get; init; } - public override string ToString() => $"{base.ToString()} {LabelPrefix}"; + public uint ListenerCount { get; init; } - public string InitializePropertiesLabel => LabelPrefix + "_prop"; - public string InitializeLocalInputLabel => LabelPrefix + "_locl"; - public string InitializeContentLabel => LabelPrefix + "_cont"; + public string BaseTypeName { get; init; } - public string InitialEvaluateOffsetsLabelPrefix => LabelPrefix + "_evali_"; - public string FinalEvaluateOffsetsLabelPrefix => LabelPrefix + "_evalf_"; - public string RefreshGroupOffsetsLabelPrefix => LabelPrefix + "_rfsh_"; + public override string ToString() => $"{base.ToString()} {LabelPrefix} {ListenerCount} {BaseTypeName}"; + + public string InitializePropertiesLabel => GetInitializePropertiesLabel(LabelPrefix); + public string InitializeLocalsInputLabel => GetInitializeLocalsInputLabel(LabelPrefix); + public string InitializeContentLabel => GetInitializeContentLabel(LabelPrefix); + + public string InitialEvaluateOffsetsLabelPrefix => GetInitialEvaluateOffsetsLabelPrefix(LabelPrefix); + public string FinalEvaluateOffsetsLabelPrefix => GetInitialEvaluateOffsetsLabelPrefix(LabelPrefix); + public string RefreshGroupOffsetsLabelPrefix => GetRefreshGroupOffsetsLabelPrefix(LabelPrefix); + + public static string GetInitializePropertiesLabel(string prefix) => prefix + "_prop"; + public static string GetInitializeLocalsInputLabel(string prefix) => prefix + "_locl"; + public static string GetInitializeContentLabel(string prefix) => prefix + "_cont"; + public static string GetInitialEvaluateOffsetsLabelPrefix(string prefix) => prefix + "_evali_"; + public static string GetFinalEvaluateOffsetsLabelPrefix(string prefix) => prefix + "_evalf_"; + public static string GetRefreshGroupOffsetsLabelPrefix(string prefix) => prefix + "_rfsh_"; }