Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -744,8 +744,8 @@ private void ProcessCachedGroupOnWide(WideViewHeaderInfo wvhi, List<PacketInfoDa
}

/// <summary>
/// In cases like implicit remoting, there is no console so reading the console width results in an exception.
/// Instead of handling exception every time we cache this value to increase performance.
/// In cases like implicit remoting, there is no console so reading the console width results in an exception.
/// Instead of handling exception every time we cache this value to increase performance.
/// </summary>
static private bool _noConsole = false;

Expand Down Expand Up @@ -940,6 +940,8 @@ internal override void Initialize()
{
TableFormattingHint tableHint = this.InnerCommand.RetrieveFormattingHint() as TableFormattingHint;
int[] columnWidthsHint = null;
// We expect that console width is less then 120.
const int columnsThresHold = 120;

if (tableHint != null)
{
Expand All @@ -955,10 +957,10 @@ internal override void Initialize()
}

// create arrays for widths and alignment
int[] columnWidths = new int[columns];
int[] alignment = new int[columns];
int k = 0;
Span<int> columnWidths = columns < columnsThresHold ? stackalloc int[columns] : new int[columns];
Span<int> alignment = columns < columnsThresHold ? stackalloc int[columns] : new int[columns];

int k = 0;
foreach (TableColumnInfo tci in this.CurrentTableHeaderInfo.tableColumnInfoList)
{
columnWidths[k] = (columnWidthsHint != null) ? columnWidthsHint[k] : tci.width;
Expand Down Expand Up @@ -1006,7 +1008,7 @@ internal override void ProcessPayload(FormatEntryData fed)

// need to make sure we have matching counts: the header count will have to prevail
string[] values = new string[headerColumns];
int[] alignment = new int[headerColumns];
Span<int> alignment = stackalloc int[headerColumns];
Copy link
Member

@daxian-dbw daxian-dbw Apr 13, 2018

Choose a reason for hiding this comment

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

Same here, alignment is passed to another method GenerateRow, so it's not safe to use stackalloc here.

Copy link
Member

Choose a reason for hiding this comment

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

It looks to me that GenerateRow uses alignment before this function returns and therefore should be safe.
Additionally, if the compiler can detect this condition as the blog suggests (per @iSazonov's comment), then we should rely on the compiler's analysis unless we find the compiler's analysis deficient.

Copy link
Collaborator Author

@iSazonov iSazonov Apr 13, 2018

Choose a reason for hiding this comment

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

I should add that Span<T> is managed code.
Before C# 7.2 we have some protections for Span<T> only in runtime.
Since C# 7.2 Roslyn does additional checks at compile time and exclude unapproved usage.

Copy link
Member

Choose a reason for hiding this comment

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

I understand the C# compiler can catch an issue like that, but the person who is facing that compilation error might not be the one who used stackalloc in the first place.

Imagine I will change the implementation of GenerateRow at some later time and my change would actually cause the unsafe reference to happen, and that means I will see the compilation error but have no idea why it fails. After finally figuring out why this could happen, I will need to search all callers of this methods and maybe the callers of those callers and so on to find "Ah, there is a stackalloc here".

Therefore, I think we should have a guideline about when to use stackalloc, so things can be less complex.

Copy link
Member

Choose a reason for hiding this comment

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

I agree that the error might be confusing to another contributor.

If we have guidance on when span and stackalloc can be used it should be clear and easy to follow.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@SteveL-MSFT They don't support Alpine in 2.0 and support in 2.1 Prview1 and perhaps in Preview2

Copy link
Member

@daxian-dbw daxian-dbw Apr 15, 2018

Choose a reason for hiding this comment

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

So in your example return MethodTwo(arr); will throw an error in any case (Span and Slice).

I think you didn't get my point.

What I try to point out is the use of stackalloc might make future changes raise compilation error at an unexpected location.

Use the same example, say initially methods MethodOne and MethodTwo looked like this:

Span<int> MethodOne(...) {
    int[] arr = new int[len];
    return MethodTwo(arr); // <-- return the span returned from another method
}

Span<int> MethodTwo(Span<int> span, ...) {
    int[] array = new int[length];
    ...
    return array.slice(...); // <-- return a slice of an array
}

So when you changed to use stackalloc in MethodOne, it would compiles fine, and the methods became:

Span<int> MethodOne(...) {
    Span<int> arr = stackalloc int[len];
    return MethodTwo(arr); // <-- return the span returned from another method
}

Span<int> MethodTwo(Span<int> span, ...) {
    int[] array = new int[length];
    ...
    return array.slice(...); // <-- return a slice of an array
}

Then later, another contributor changes MethodTwo to return a sclice of the pass-in argument span like this:

Span<int> MethodTwo(Span<int> span, ...) {
    ....
    return span.Slice(...); // <-- return a slice of the passed-in span
}

And then the contributor will see the unexpected compilation error from MethodOne.

This is the situation that I want to avoid with some coding guidelines. So what should be the guideline in this situation? Should it be:

  • Do not return a slice of a passed-in span argument in a method? (attempt to prevent this situation from MehtodTwo)
  • Or, do not directly return a span result returned from another method when you pass in stack memory as an argument to that method? (attempt to prevent this situation from MethodOne)

Hope I made myself clear this time :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I see your point. I believe we could compare this with changing in a base class - we can catch tons compilation errors in derived classes - this is the same situation. Another example is a changing in a helper class - we can get compilation errors in each call point.
And it seems like your example is too incredible if we try to imagine a real situation - such a change in the code completely changes its contract as if Array. Sort would start to do a clone-then-sort. By the way, in the latter case we may get an unpredictable run-time error or nothing worse. So Span gives even more reliable code.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think this is something that we will have to get used to. It is a new and very welcome addition to the .net runtime, and we as developers need to learn to troubleshoot the issues that arises.
I say let the compilation error happen, and add answers to StackOverflow on how to resolve and understand them.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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


int fieldCount = tre.formatPropertyFieldList.Count;

Expand Down Expand Up @@ -1168,8 +1170,8 @@ internal override void Initialize()
_buffer = new StringValuesBuffer(itemsPerRow);

// initialize the writer
int[] columnWidths = new int[itemsPerRow];
int[] alignment = new int[itemsPerRow];
Span<int> columnWidths = stackalloc int[itemsPerRow];
Span<int> alignment = stackalloc int[itemsPerRow];

for (int k = 0; k < itemsPerRow; k++)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;

namespace Microsoft.PowerShell.Commands.Internal.Format
{
/// <summary>
Expand Down Expand Up @@ -29,7 +31,7 @@ internal ColumnWidthManager(int tableWidth, int minimumColumnWidth, int separato
/// with column elimination, starting from the right most column
/// </summary>
/// <param name="columnWidths">array of column widths to appropriately size</param>
internal void CalculateColumnWidths(int[] columnWidths)
internal void CalculateColumnWidths(Span<int> columnWidths)
{
if (AssignColumnWidths(columnWidths))
{
Expand All @@ -47,7 +49,7 @@ internal void CalculateColumnWidths(int[] columnWidths)
/// </summary>
/// <param name="columnWidths">columns to process</param>
/// <returns>true if there was a fit, false if there is need for trimming</returns>
private bool AssignColumnWidths(int[] columnWidths)
private bool AssignColumnWidths(Span<int> columnWidths)
{
// run a quick check to see if all the columns have a specified width,
// if so, we are done
Expand Down Expand Up @@ -127,7 +129,7 @@ private bool AssignColumnWidths(int[] columnWidths)
/// trim columns if the total column width is too much for the screen.
/// </summary>
/// <param name="columnWidths">column widths to trim</param>
private void TrimToFit(int[] columnWidths)
private void TrimToFit(Span<int> columnWidths)
{
while (true)
{
Expand Down Expand Up @@ -166,7 +168,7 @@ private void TrimToFit(int[] columnWidths)
/// </summary>
/// <param name="columnWidths">column widths array</param>
/// <returns></returns>
private int CurrentTableWidth(int[] columnWidths)
private int CurrentTableWidth(Span<int> columnWidths)
{
int sum = 0;
int visibleColumns = 0;
Expand All @@ -188,7 +190,7 @@ private int CurrentTableWidth(int[] columnWidths)
/// </summary>
/// <param name="columnWidths">column widths array</param>
/// <returns>index of the last visible column, -1 if none</returns>
private static int GetLastVisibleColumn(int[] columnWidths)
private static int GetLastVisibleColumn(Span<int> columnWidths)
{
for (int k = 0; k < columnWidths.Length; k++)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -700,12 +700,16 @@ internal static string StripNewLines (string s)
internal static string TruncateAtNewLine(string s)
{
if (string.IsNullOrEmpty(s))
return s;
{
return "";
}

int lineBreak = s.IndexOfAny(s_lineBreakChars);

if (lineBreak < 0)
{
return s;
}

return s.Substring(0, lineBreak) + PSObjectHelper.ellipses;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Management.Automation.Internal;
using System.Text;
Expand Down Expand Up @@ -75,7 +77,7 @@ internal static int ComputeWideViewBestItemsPerRowFit(int stringLen, int screenC
/// <param name="columnWidths">array of specified column widths</param>
/// <param name="alignment">array of alignment flags</param>
/// <param name="suppressHeader">if true, suppress header printing</param>
internal void Initialize(int leftMarginIndent, int screenColumns, int[] columnWidths, int[] alignment, bool suppressHeader)
internal void Initialize(int leftMarginIndent, int screenColumns, Span<int> columnWidths, ReadOnlySpan<int> alignment, bool suppressHeader)
{
//Console.WriteLine(" 1 2 3 4 5 6 7");
//Console.WriteLine("01234567890123456789012345678901234567890123456789012345678901234567890123456789");
Expand Down Expand Up @@ -161,7 +163,7 @@ internal void GenerateHeader(string[] values, LineOutput lo)
// generate an array of "--" as header markers below
// the column header labels
string[] breakLine = new string[values.Length];
for (int k = 0; k < _si.columnInfo.Length; k++)
for (int k = 0; k < breakLine.Length; k++)
{
// the column can be hidden
if (_si.columnInfo[k].width <= 0)
Expand All @@ -185,25 +187,25 @@ internal void GenerateHeader(string[] values, LineOutput lo)
GenerateRow(breakLine, lo, false, null, lo.DisplayCells);
}

internal void GenerateRow(string[] values, LineOutput lo, bool multiLine, int[] alignment, DisplayCells dc)
internal void GenerateRow(string[] values, LineOutput lo, bool multiLine, ReadOnlySpan<int> alignment, DisplayCells dc)
{
if (_disabled)
return;

// build the current row alignment settings
int cols = _si.columnInfo.Length;
int[] currentAlignment = new int[cols];
Span<int> currentAlignment = stackalloc int[cols];

if (alignment == null)
{
for (int i = 0; i < cols; i++)
for (int i = 0; i < currentAlignment.Length; i++)
{
currentAlignment[i] = _si.columnInfo[i].alignment;
}
}
else
{
for (int i = 0; i < cols; i++)
for (int i = 0; i < currentAlignment.Length; i++)
{
if (alignment[i] == TextAlignment.Undefined)
currentAlignment[i] = _si.columnInfo[i].alignment;
Expand All @@ -227,10 +229,10 @@ internal void GenerateRow(string[] values, LineOutput lo, bool multiLine, int[]
}
}

private string[] GenerateTableRow(string[] values, int[] alignment, DisplayCells ds)
private string[] GenerateTableRow(string[] values, ReadOnlySpan<int> alignment, DisplayCells ds)
{
// select the active columns (skip hidden ones)
int[] validColumnArray = new int[_si.columnInfo.Length];
Span<int> validColumnArray = stackalloc int[_si.columnInfo.Length];
int validColumnCount = 0;
for (int k = 0; k < _si.columnInfo.Length; k++)
{
Expand All @@ -241,7 +243,9 @@ private string[] GenerateTableRow(string[] values, int[] alignment, DisplayCells
}

if (validColumnCount == 0)
{
return null;
}

StringCollection[] scArray = new StringCollection[validColumnCount];
bool addPadding = true;
Expand Down Expand Up @@ -353,7 +357,7 @@ private string[] GenerateTableRow(string[] values, int[] alignment, DisplayCells

// finally, build an array of strings
string[] rows = new string[screenRows];
for (int row = 0; row < rows.Length; row++)
for (int row = 0; row < screenRows; row++)
{
StringBuilder sb = new StringBuilder();
// for a given row, walk the columns
Expand All @@ -371,6 +375,7 @@ private string[] GenerateTableRow(string[] values, int[] alignment, DisplayCells
}
rows[row] = sb.ToString();
}

return rows;
}

Expand All @@ -390,7 +395,7 @@ private StringCollection GenerateMultiLineRowField(string val, int k, int alignm
return sc;
}

private string GenerateRow(string[] values, int[] alignment, DisplayCells dc)
private string GenerateRow(string[] values, ReadOnlySpan<int> alignment, DisplayCells dc)
{
StringBuilder sb = new StringBuilder();

Expand All @@ -408,7 +413,6 @@ private string GenerateRow(string[] values, int[] alignment, DisplayCells dc)
// skip columns that are not at least a single character wide
continue;
}
int newRowIndex = sb.Length;

// NOTE: the following padding operations assume that we
// pad with a blank (or any character that ALWAYS maps to a single screen cell
Expand All @@ -432,7 +436,7 @@ private string GenerateRow(string[] values, int[] alignment, DisplayCells dc)
private static string GenerateRowField(string val, int width, int alignment, DisplayCells dc, bool addPadding)
{
// make sure the string does not have any embedded <CR> in it
string s = StringManipulationHelper.TruncateAtNewLine(val) ?? "";
string s = StringManipulationHelper.TruncateAtNewLine(val);

string currentValue = s;
int currentValueDisplayLength = dc.Length(currentValue);
Expand Down