Общий алгоритм генерации конечного автомата для yield: различия между версиями
Mikst (обсуждение | вклад) (→Пример) |
Mikst (обсуждение | вклад) |
||
Строка 122: | Строка 122: | ||
Result := False | Result := False | ||
exit; | exit; | ||
end | |||
</source> | |||
==== Новый способ генерации конечного автомата ==== | |||
При новом способе код получается проще и чище. Мы приводим только код MoveNext() | |||
<source lang="Delphi"> | |||
function MoveNext(): boolean; | |||
label lb#1,lb#2; | |||
label lbstate#0,lbstate#1; | |||
begin | |||
Result := False | |||
if <>1__state = 0 then | |||
goto lbstate#0 | |||
else | |||
goto lbstate#1 | |||
lbstate#0: | |||
<$coll$1>MethodLocalVariable__1 := <>MethodFormalParam__a | |||
<$enumerator$1>MethodLocalVariable__2 := <$coll$1>MethodLocalVariable__1.GetEnumerator() | |||
lb#2: | |||
if not <$enumerator$1>MethodLocalVariable__2.MoveNext() then | |||
goto lb#1 | |||
<$x1>MethodLocalVariable__3 := <$enumerator$1>MethodLocalVariable__2.Current | |||
<>2__current := <$x1>MethodLocalVariable__3 | |||
<>1__state := 1 | |||
Result := True | |||
exit | |||
lbstate#1: | |||
goto lb#2 | |||
lb#1: | |||
end | end | ||
</source> | </source> |
Версия от 14:58, 7 июля 2016
Конечный автомат генерируется по коду тела функции, содержащей yield, после Lowering.
Вначале был реализован алгоритм как в C# - с помощью case (switch) по состояниям. Этот алгоритм включал в себя некоторую сложность: при наличии меток после lowering часть кода после метки приходилось выносить за case и организовывалась сложная логика переходов.
Последней каплей стал автовывод типов с узлами auto_type - присваивания для автовывода должны быть записаны в естественном порядке.
Поэтому было решено заменить case на блок вложенных if в начале с переходами по меткам. Этот способ по генерации кода оказывается проще и равномернее: если внутри кода встречается своя метка, то это никак не надло специально обрабатывать.
Кроме того, тесты показали, что код с вложенными if в PascalABC.NET работает быстрее кода с case.
Общий алгоритм
1. Количество yield равно количеству состояний плюс нулевое состояние
2. Сформировать в начале секцию переходов с помощью if
3. После секции переходов помечать меткой текущего состояния и от текущего места до yield переносить код. Код yield expr заменить на
current := <yielded-value>
state := <next-state>
Result := true
exit
4. В заключительном состоянии в конце Result := False. Или сделать это в начале алгоритма
Пример
Функция
function f<T>(a: array of T): sequence of T;
begin
foreach var x in a do
yield x;
end;
после Loweringа заменяется на следующую:
function <yield_helper_locals_type_detector>f<T>(a: array of T): sequence of T;
label lb#1,lb#2;
begin
var $coll$1: IEnumerable<yield_unknown_foreach_type>;
$coll$1 := a
var $enumerator$1 := $coll$1.GetEnumerator();
lb#2:
if not $enumerator$1.MoveNext() then
goto lb#1
var $x1 := $enumerator$1.Current;
yield $x1
goto lb#2
lb#1:
end
Старый способ генерации конечного автомата
В результате генерации конечного автомата получаем старым методом с case получаем следующий код:
type
clyield#1 = class(IEnumerable<T>,IEnumerator<T>)
public_modifer
<$coll$1>MethodLocalVariable__1: IEnumerable<yield_unknown_foreach_type>;
<$enumerator$1>MethodLocalVariable__2: yield_unknown_expression_type;
<$x1>MethodLocalVariable__3: yield_unknown_expression_type;
<>MethodFormalParam__a: array of T;
<>1__state: integer;
<>2__current: T;
...
function MoveNext(): boolean;
label lb#1,lb#2;
begin
case <>1__state of
0:
begin
<$coll$1>MethodLocalVariable__1 := <>MethodFormalParam__a
<$enumerator$1>MethodLocalVariable__2 := <$coll$1>MethodLocalVariable__1.GetEnumerator()
goto lb#2
end
1:
begin
goto lb#2
goto lb#1
exit
end
end;
lb#2:
if not <$enumerator$1>MethodLocalVariable__2.MoveNext() then
goto lb#1
<$x1>MethodLocalVariable__3 := <$enumerator$1>MethodLocalVariable__2.Current
<>2__current := <$x1>MethodLocalVariable__3
<>1__state := 1
Result := True
exit
lb#1:
end
Здесь хорошо видно, что количество состояний на один больше количества yield.
Новым методом получается следующий код:
function MoveNext(): boolean;
label lb#1,lb#2,lbstate#0,lbstate#1;
begin
Result := False;
if <>1__state = 0 then
goto lbstate#0;
if <>1__state = 1 then
goto lbstate#1;
lbstate#0:
<$coll$1>MethodLocalVariable__1 := <>MethodFormalParam__a
<$enumerator$1>MethodLocalVariable__2 := <$coll$1>MethodLocalVariable__1.GetEnumerator()
lb#2:
if not <$enumerator$1>MethodLocalVariable__2.MoveNext() then
goto lb#1
<$x1>MethodLocalVariable__3 := <$enumerator$1>MethodLocalVariable__2.Current
<>2__current := <$x1>MethodLocalVariable__3
<>1__state := 1
Result := True
exit
lbstate#1:
goto lb#2;
lb#1:
Result := False
exit;
end
Новый способ генерации конечного автомата
При новом способе код получается проще и чище. Мы приводим только код MoveNext()
function MoveNext(): boolean;
label lb#1,lb#2;
label lbstate#0,lbstate#1;
begin
Result := False
if <>1__state = 0 then
goto lbstate#0
else
goto lbstate#1
lbstate#0:
<$coll$1>MethodLocalVariable__1 := <>MethodFormalParam__a
<$enumerator$1>MethodLocalVariable__2 := <$coll$1>MethodLocalVariable__1.GetEnumerator()
lb#2:
if not <$enumerator$1>MethodLocalVariable__2.MoveNext() then
goto lb#1
<$x1>MethodLocalVariable__3 := <$enumerator$1>MethodLocalVariable__2.Current
<>2__current := <$x1>MethodLocalVariable__3
<>1__state := 1
Result := True
exit
lbstate#1:
goto lb#2
lb#1:
end