source: trunk/UBFTarget.pas @ 114

Last change on this file since 114 was 114, checked in by chronos, 15 months ago
  • Modified: Improved stepping through source and target code. Each step has source, program and target index.
File size: 20.0 KB
Line 
1unit UBFTarget;
2
3{$mode delphi}
4
5interface
6
7uses
8  Classes, SysUtils, UTarget;
9
10type
11
12  TMachineCommand = (cmNoOperation, cmInc, cmDec, cmPointerInc, cmPointerDec,
13    cmOutput, cmInput, cmLoopStart, cmLoopEnd, cmDebug, cmSet, cmMultipy);
14
15  { TMachineOperation }
16
17  TMachineOperation = record
18    Command: TMachineCommand;
19    Parameter: Integer;
20    RelIndex: Integer;
21    function Create(Command: TMachineCommand; Parameter, RelIndex: Integer): TMachineOperation;
22  end;
23
24  TOptimizations = record
25    AddSub: Boolean;
26    Merge: Boolean;
27    RelativeIndexes: Boolean;
28    CopyMultiply: Boolean;
29  end;
30
31  { TBFTarget }
32
33  TBFTarget = class(TTarget)
34  private
35    function CheckClear: Boolean;
36    function CheckOccurenceSumParam(C: TMachineCommand): Integer;
37    function CheckOccurence(C: TMachineCommand): Integer;
38    procedure OptimizeAddSub;
39    procedure OptimizeMerge;
40    procedure OptimizeZeroInitMemory;
41    procedure OptimizeRelativeIndexes;
42    procedure OptimizeCopyMultiply;
43  protected
44    FProgram: array of TMachineOperation;
45    FProgramIndex: Integer;
46    function GetOperationText(Operation: TMachineOperation): string; virtual;
47    procedure LoadProgram; override;
48  public
49    MemorySize: Integer;
50    MemoryMaxUsedAddr: Integer;
51    CellSize: Integer;
52    Optimizations: TOptimizations;
53    constructor Create; override;
54    procedure OptimizeSource; override;
55    property ProgramIndex: Integer read FProgramIndex;
56  end;
57
58const
59  BrainFuckCommandText: array[TMachineCommand] of Char = (
60    ' ', '+', '-', '>', '<', '.', ',', '[', ']', '@', '=', '*');
61
62
63implementation
64
65{ TMachineOperation }
66
67function TMachineOperation.Create(Command: TMachineCommand; Parameter,
68  RelIndex: Integer): TMachineOperation;
69begin
70  Result.Command := Command;
71  Result.Parameter := Parameter;
72  Result.RelIndex := RelIndex;
73end;
74
75function TBFTarget.CheckClear: Boolean;
76begin
77  Result := (FProgram[FProgramIndex].Command = cmLoopStart) and (Length(FProgram) >= FProgramIndex + 2) and
78    (((FProgram[FProgramIndex + 1].Command = cmDec) and (FProgram[FProgramIndex + 1].Parameter = 1)) or
79    ((FProgram[FProgramIndex + 1].Command = cmInc) and (FProgram[FProgramIndex + 1].Parameter = -1)))
80    and (FProgram[FProgramIndex + 2].Command = cmLoopEnd);
81end;
82
83function TBFTarget.CheckOccurence(C: TMachineCommand): Integer;
84begin
85  Result := 1;
86  while ((FProgramIndex + 1) < Length(FProgram)) and (FProgram[FProgramIndex + 1].Command = C) do begin
87    Inc(Result);
88    Inc(FProgramIndex);
89  end;
90end;
91
92function TBFTarget.CheckOccurenceSumParam(C: TMachineCommand): Integer;
93begin
94  Result := FProgram[FProgramIndex].Parameter;
95  while ((FProgramIndex + 1) < Length(FProgram)) and (FProgram[FProgramIndex + 1].Command = C) do begin
96    Inc(Result, FProgram[FProgramIndex + 1].Parameter);
97    Inc(FProgramIndex);
98  end;
99end;
100
101procedure TBFTarget.OptimizeAddSub;
102var
103  NewProgram: array of TMachineOperation;
104  NewProgramIndex: Integer;
105  NewTargetPos: Integer;
106  FirstIndex: Integer;
107begin
108  NewProgramIndex := 0;
109  NewTargetPos := 0;
110  SetLength(NewProgram, Length(FProgram));
111
112  FProgramIndex := 0;
113  while (FProgramIndex < Length(FProgram)) do begin
114    FirstIndex := FProgramIndex;
115    case FProgram[FProgramIndex].Command of
116      cmPointerInc: begin
117        NewProgram[NewProgramIndex].Command := cmPointerInc;
118        NewProgram[NewProgramIndex].Parameter := CheckOccurenceSumParam(cmPointerInc);
119      end;
120      cmPointerDec: begin
121        NewProgram[NewProgramIndex].Command := cmPointerDec;
122        NewProgram[NewProgramIndex].Parameter := CheckOccurenceSumParam(cmPointerDec);
123      end;
124      cmInc: begin
125        NewProgram[NewProgramIndex].Command := cmInc;
126        NewProgram[NewProgramIndex].Parameter := CheckOccurenceSumParam(cmInc);
127      end;
128      cmDec: begin
129        NewProgram[NewProgramIndex].Command := cmDec;
130        NewProgram[NewProgramIndex].Parameter := CheckOccurenceSumParam(cmDec);
131      end;
132      else NewProgram[NewProgramIndex] := FProgram[FProgramIndex];
133    end;
134    DebugSteps.UpdateTargetPos(FirstIndex, FProgramIndex, NewProgramIndex, NewTargetPos);
135    Inc(NewTargetPos, Length(GetOperationText(NewProgram[NewProgramIndex])));
136    Inc(FProgramIndex);
137    Inc(NewProgramIndex);
138  end;
139  SetLength(NewProgram, NewProgramIndex);
140
141  // Replace old program by new program
142  SetLength(FProgram, Length(NewProgram));
143  Move(Pointer(NewProgram)^, Pointer(FProgram)^, SizeOf(TMachineOperation) * Length(NewProgram));
144end;
145
146procedure TBFTarget.OptimizeMerge;
147var
148  NewProgram: array of TMachineOperation;
149  NewProgramIndex: Integer;
150  PreviousCommand: TMachineCommand;
151  FirstIndex: Integer;
152  NewTextIndex: Integer;
153begin
154  // Merge together cmInc, cmDec, cmSet
155  // Merge together cmPointerInc, cmPointerDec
156  PreviousCommand := cmNoOperation;
157  NewProgramIndex := 0;
158  SetLength(NewProgram, Length(FProgram));
159
160  FProgramIndex := 0;
161  NewTextIndex := 0;
162  while (FProgramIndex < Length(FProgram)) do begin
163    FirstIndex := FProgramIndex;
164    case FProgram[FProgramIndex].Command of
165      cmPointerInc: begin
166        if PreviousCommand in [cmPointerInc, cmPointerDec] then begin
167          if NewProgram[NewProgramIndex - 1].Command = cmPointerInc then
168            NewProgram[NewProgramIndex - 1].Parameter := NewProgram[NewProgramIndex - 1].Parameter +
169              FProgram[FProgramIndex].Parameter
170          else if NewProgram[NewProgramIndex - 1].Command = cmPointerDec then
171            NewProgram[NewProgramIndex - 1].Parameter := NewProgram[NewProgramIndex - 1].Parameter -
172              FProgram[FProgramIndex].Parameter;
173          // If value negative then change command
174          if NewProgram[NewProgramIndex - 1].Parameter < 0 then begin
175            NewProgram[NewProgramIndex - 1].Parameter := -NewProgram[NewProgramIndex - 1].Parameter;
176            if NewProgram[NewProgramIndex - 1].Command = cmPointerInc then
177              NewProgram[NewProgramIndex - 1].Command := cmPointerDec
178              else NewProgram[NewProgramIndex - 1].Command := cmPointerInc;
179          end;
180          if NewProgram[NewProgramIndex - 1].Parameter = 0 then Dec(NewProgramIndex);
181          Dec(NewProgramIndex);
182        end else begin
183          NewProgram[NewProgramIndex].Command := cmPointerInc;
184          NewProgram[NewProgramIndex].Parameter := FProgram[FProgramIndex].Parameter;
185        end;
186      end;
187      cmPointerDec: begin
188        if PreviousCommand in [cmPointerInc, cmPointerDec] then begin
189          if NewProgram[NewProgramIndex - 1].Command = cmPointerDec then
190            NewProgram[NewProgramIndex - 1].Parameter := NewProgram[NewProgramIndex - 1].Parameter +
191              FProgram[FProgramIndex].Parameter
192          else if NewProgram[NewProgramIndex - 1].Command = cmPointerInc then
193            NewProgram[NewProgramIndex - 1].Parameter := NewProgram[NewProgramIndex - 1].Parameter -
194              FProgram[FProgramIndex].Parameter;
195          // If value negative then change command
196          if NewProgram[NewProgramIndex - 1].Parameter < 0 then begin
197            NewProgram[NewProgramIndex - 1].Parameter := -NewProgram[NewProgramIndex - 1].Parameter;
198            if NewProgram[NewProgramIndex - 1].Command = cmPointerInc then
199              NewProgram[NewProgramIndex - 1].Command := cmPointerDec
200              else NewProgram[NewProgramIndex - 1].Command := cmPointerInc;
201          end;
202          if NewProgram[NewProgramIndex - 1].Parameter = 0 then Dec(NewProgramIndex);
203          Dec(NewProgramIndex);
204        end else begin
205          NewProgram[NewProgramIndex].Command := cmPointerDec;
206          NewProgram[NewProgramIndex].Parameter := FProgram[FProgramIndex].Parameter;
207        end;
208      end;
209      cmInc: begin
210        if PreviousCommand in [cmInc, cmDec, cmSet] then begin
211          if NewProgram[NewProgramIndex - 1].Command in [cmInc, cmSet] then
212            NewProgram[NewProgramIndex - 1].Parameter := NewProgram[NewProgramIndex - 1].Parameter +
213              FProgram[FProgramIndex].Parameter
214          else if NewProgram[NewProgramIndex - 1].Command = cmDec then
215            NewProgram[NewProgramIndex - 1].Parameter := NewProgram[NewProgramIndex - 1].Parameter -
216              FProgram[FProgramIndex].Parameter;
217          // If value negative then change command
218          if (NewProgram[NewProgramIndex - 1].Parameter < 0) and (NewProgram[NewProgramIndex - 1].Command <> cmSet) then begin
219            NewProgram[NewProgramIndex - 1].Parameter := -NewProgram[NewProgramIndex - 1].Parameter;
220            if NewProgram[NewProgramIndex - 1].Command = cmInc then
221              NewProgram[NewProgramIndex - 1].Command := cmDec
222              else NewProgram[NewProgramIndex - 1].Command := cmInc;
223          end;
224          if NewProgram[NewProgramIndex - 1].Parameter = 0 then Dec(NewProgramIndex);
225          Dec(NewProgramIndex);
226        end else begin
227          NewProgram[NewProgramIndex].Command := cmInc;
228          NewProgram[NewProgramIndex].Parameter := FProgram[FProgramIndex].Parameter;
229        end;
230      end;
231      cmDec: begin
232        if PreviousCommand in [cmInc, cmDec, cmSet] then begin
233          if NewProgram[NewProgramIndex - 1].Command = cmDec then
234            NewProgram[NewProgramIndex - 1].Parameter := NewProgram[NewProgramIndex - 1].Parameter +
235              FProgram[FProgramIndex].Parameter
236          else if NewProgram[NewProgramIndex - 1].Command in [cmInc, cmSet] then
237            NewProgram[NewProgramIndex - 1].Parameter := NewProgram[NewProgramIndex - 1].Parameter -
238              FProgram[FProgramIndex].Parameter;
239          // If value negative then change command
240          if (NewProgram[NewProgramIndex - 1].Parameter < 0) and (NewProgram[NewProgramIndex - 1].Command <> cmSet) then begin
241            NewProgram[NewProgramIndex - 1].Parameter := -NewProgram[NewProgramIndex - 1].Parameter;
242            if NewProgram[NewProgramIndex - 1].Command = cmInc then
243              NewProgram[NewProgramIndex - 1].Command := cmDec
244              else NewProgram[NewProgramIndex - 1].Command := cmInc;
245          end;
246          if NewProgram[NewProgramIndex - 1].Parameter = 0 then Dec(NewProgramIndex);
247          Dec(NewProgramIndex);
248        end else begin
249          NewProgram[NewProgramIndex].Command := cmDec;
250          NewProgram[NewProgramIndex].Parameter := FProgram[FProgramIndex].Parameter;
251        end;
252      end;
253      cmSet: begin
254        if PreviousCommand in [cmInc, cmDec, cmSet] then begin
255          // Set overrides value of previous commands
256          Dec(NewProgramIndex);
257          NewProgram[NewProgramIndex].Command := cmSet;
258          NewProgram[NewProgramIndex].Parameter := FProgram[FProgramIndex].Parameter;
259        end else begin
260          NewProgram[NewProgramIndex] := FProgram[FProgramIndex];
261        end;
262      end;
263      cmLoopStart: begin
264        if CheckClear then begin
265          NewProgram[NewProgramIndex] := TMachineOperation.Create(cmSet, 0, 0);
266          Inc(FProgramIndex, 2);
267        end else begin
268          NewProgram[NewProgramIndex] := FProgram[FProgramIndex];
269        end;
270      end;
271      else NewProgram[NewProgramIndex] := FProgram[FProgramIndex];
272    end;
273    PreviousCommand := FProgram[FProgramIndex].Command;
274    DebugSteps.UpdateTargetPos(FirstIndex, FProgramIndex, NewProgramIndex, NewTextIndex);
275    Inc(NewTextIndex, Length(GetOperationText(NewProgram[NewProgramIndex])));
276    Inc(FProgramIndex);
277    Inc(NewProgramIndex);
278  end;
279  SetLength(NewProgram, NewProgramIndex);
280
281  // Replace old program by new program
282  SetLength(FProgram, Length(NewProgram));
283  Move(Pointer(NewProgram)^, Pointer(FProgram)^, SizeOf(TMachineOperation) * Length(NewProgram));
284end;
285
286procedure TBFTarget.OptimizeZeroInitMemory;
287begin
288  // Here Optimizations related to assumption that initial memory is filled with zeroes
289  // Then code for constants preparation can be translated to cmSet commands
290  // To eliminate also loops for building constants code need to be somehow interpretted partialy
291end;
292
293procedure TBFTarget.OptimizeRelativeIndexes;
294var
295  NewProgram: array of TMachineOperation;
296  NewProgramIndex: Integer;
297  RelIndex: Integer;
298  FirstIndex: Integer;
299  NewTextIndex: Integer;
300begin
301  NewProgramIndex := 0;
302  SetLength(NewProgram, Length(FProgram));
303
304  RelIndex := 0;
305  FProgramIndex := 0;
306  NewTextIndex := 0;
307  while (FProgramIndex < Length(FProgram)) do begin
308    FirstIndex := FProgramIndex;
309    case FProgram[FProgramIndex].Command of
310      cmPointerInc: begin
311        RelIndex := RelIndex + FProgram[FProgramIndex].Parameter;
312        Dec(NewProgramIndex);
313      end;
314      cmPointerDec: begin
315        RelIndex := RelIndex - FProgram[FProgramIndex].Parameter;
316        Dec(NewProgramIndex);
317      end;
318      cmInc, cmDec, cmInput, cmOutput, cmSet: begin
319        NewProgram[NewProgramIndex] := FProgram[FProgramIndex];
320        NewProgram[NewProgramIndex].RelIndex :=
321          NewProgram[NewProgramIndex].RelIndex + RelIndex;
322      end;
323      cmLoopStart, cmLoopEnd: begin
324        if RelIndex > 0 then begin
325          NewProgram[NewProgramIndex] := TMachineOperation.Create(cmPointerInc,
326            RelIndex, 0);
327          Inc(NewProgramIndex);
328          RelIndex := 0;
329        end else
330        if RelIndex < 0 then begin
331          NewProgram[NewProgramIndex] := TMachineOperation.Create(cmPointerDec,
332            Abs(RelIndex), 0);
333          Inc(NewProgramIndex);
334          RelIndex := 0;
335        end;
336        NewProgram[NewProgramIndex] := FProgram[FProgramIndex];
337      end;
338      else raise Exception.Create(Format('Unsupported command %d', [FProgram[FProgramIndex].Command]));
339    end;
340    DebugSteps.UpdateTargetPos(FirstIndex, FProgramIndex, NewProgramIndex, NewTextIndex);
341    Inc(NewTextIndex, Length(GetOperationText(NewProgram[NewProgramIndex])));
342    Inc(FProgramIndex);
343    Inc(NewProgramIndex);
344  end;
345  SetLength(NewProgram, NewProgramIndex);
346
347  // Replace old program by new program
348  SetLength(FProgram, Length(NewProgram));
349  Move(Pointer(NewProgram)^, Pointer(FProgram)^, SizeOf(TMachineOperation) *
350    Length(NewProgram));
351end;
352
353procedure TBFTarget.OptimizeCopyMultiply;
354var
355  NewProgram: array of TMachineOperation;
356  NewProgramIndex: Integer;
357  ProcessLoop: Boolean;
358  PointerChange: Integer;
359  NumberOfBaseDecrement: Integer;
360  LoopStartIndex: Integer;
361  LoopStartIndexNew: Integer;
362  FirstIndex: Integer;
363  NewTextIndex: Integer;
364begin
365  NewProgramIndex := 0;
366  SetLength(NewProgram, Length(FProgram));
367
368  NumberOfBaseDecrement := 0;
369  ProcessLoop := False;
370  FProgramIndex := 0;
371  NewTextIndex := 0;
372  PointerChange := 0;
373  while (FProgramIndex < Length(FProgram)) do begin
374    FirstIndex := FProgramIndex;
375    case FProgram[FProgramIndex].Command of
376      cmPointerInc: begin
377        PointerChange := PointerChange + FProgram[FProgramIndex].Parameter;
378        NewProgram[NewProgramIndex] := FProgram[FProgramIndex];
379      end;
380      cmPointerDec: begin
381        PointerChange := PointerChange - FProgram[FProgramIndex].Parameter;
382        NewProgram[NewProgramIndex] := FProgram[FProgramIndex];
383      end;
384      cmInc: begin
385        if not ProcessLoop then begin
386          NewProgram[NewProgramIndex] := FProgram[FProgramIndex];
387        end else begin
388          if ((FProgram[FProgramIndex].RelIndex + PointerChange) <> 0) then begin
389            NewProgram[NewProgramIndex] := FProgram[FProgramIndex];
390            NewProgram[NewProgramIndex].Command := cmMultipy;
391          end else Dec(NewProgramIndex);
392        end;
393      end;
394      cmDec: begin
395        if not ProcessLoop then begin
396          if (PointerChange = 0) and (FProgram[FProgramIndex].RelIndex = 0) and
397            (FProgram[FProgramIndex].Parameter = 1) then
398            Inc(NumberOfBaseDecrement);
399          NewProgram[NewProgramIndex] := FProgram[FProgramIndex];
400        end else begin
401          if ((FProgram[FProgramIndex].RelIndex + PointerChange) <> 0) then begin
402            NewProgram[NewProgramIndex] := FProgram[FProgramIndex];
403            NewProgram[NewProgramIndex].Command := cmMultipy;
404            NewProgram[NewProgramIndex].Parameter := -FProgram[FProgramIndex].Parameter;
405          end else Dec(NewProgramIndex);
406        end;
407      end;
408      cmInput, cmOutput: begin
409        NewProgram[NewProgramIndex] := FProgram[FProgramIndex];
410        Inc(NumberOfBaseDecrement, 2);
411      end;
412      cmSet: begin
413        NewProgram[NewProgramIndex] := FProgram[FProgramIndex];
414        Inc(NumberOfBaseDecrement, 2);
415      end;
416      cmLoopStart: begin
417        if not ProcessLoop then begin
418          NumberOfBaseDecrement := 0;
419          PointerChange := 0;
420          LoopStartIndex := FProgramIndex;
421          LoopStartIndexNew := NewProgramIndex;
422          NewProgram[NewProgramIndex] := FProgram[FProgramIndex];
423        end else begin
424          Dec(NewProgramIndex);
425        end;
426      end;
427      cmLoopEnd: begin
428        if not ProcessLoop then begin
429          if (NumberOfBaseDecrement = 1) and (PointerChange = 0) then begin
430            FProgramIndex := LoopstartIndex - 1;
431            NewProgramIndex := LoopStartIndexNew - 1;
432            ProcessLoop := True;
433          end else begin
434            NewProgram[NewProgramIndex] := FProgram[FProgramIndex];
435          end;
436        end else begin
437          NewProgram[NewProgramIndex] := TMachineOperation.Create(cmSet, 0, 0);
438          ProcessLoop := False;
439          NumberOfBaseDecrement := 0;
440        end;
441      end;
442      else raise Exception.Create(Format('Unsupported command %d', [FProgram[FProgramIndex].Command]));
443    end;
444    DebugSteps.UpdateTargetPos(FirstIndex, FProgramIndex, NewProgramIndex, NewTextIndex);
445    Inc(NewTextIndex, Length(GetOperationText(NewProgram[NewProgramIndex])));
446    Inc(FProgramIndex);
447    Inc(NewProgramIndex);
448  end;
449  SetLength(NewProgram, NewProgramIndex);
450
451  // Replace old program by new program
452  SetLength(FProgram, Length(NewProgram));
453  Move(Pointer(NewProgram)^, Pointer(FProgram)^, SizeOf(TMachineOperation) *
454    Length(NewProgram));
455end;
456
457function TBFTarget.GetOperationText(Operation: TMachineOperation): string;
458begin
459  Result := BrainFuckCommandText[Operation.Command];
460  if Operation.Command in [cmInc, cmDec, cmPointerInc, cmPointerDec,
461    cmSet, cmMultipy] then begin
462  if Operation.Parameter <> 1 then
463    Result := Result + IntToStr(Operation.Parameter);
464  end;
465  if Operation.RelIndex <> 0 then
466    Result := Result + 'R' + IntToStr(Operation.RelIndex);
467end;
468
469procedure TBFTarget.LoadProgram;
470var
471  I: Integer;
472begin
473  inherited;
474  DebugSteps.Clear;
475  SetLength(FProgram, Length(FSourceCode));
476  FProgramIndex := 0;
477  for I := 1 to Length(FSourceCode) do begin
478    case FSourceCode[I] of
479      '+': begin
480        FProgram[FProgramIndex] := TMachineOperation.Create(cmInc, 1, 0);
481        DebugSteps.AddStep(I - 1, FProgramIndex, soNormal);
482      end;
483      '-': begin
484        FProgram[FProgramIndex] := TMachineOperation.Create(cmDec, 1, 0);
485        DebugSteps.AddStep(I - 1, FProgramIndex, soNormal);
486      end;
487      '>': begin
488        FProgram[FProgramIndex] := TMachineOperation.Create(cmPointerInc, 1, 0);
489        DebugSteps.AddStep(I - 1, FProgramIndex, soNormal);
490      end;
491      '<': begin
492        FProgram[FProgramIndex] := TMachineOperation.Create(cmPointerDec, 1, 0);
493        DebugSteps.AddStep(I - 1, FProgramIndex, soNormal);
494      end;
495      ',': begin
496        FProgram[FProgramIndex] := TMachineOperation.Create(cmInput, 0, 0);
497        DebugSteps.AddStep(I - 1, FProgramIndex, soNormal);
498      end;
499      '.': begin
500        FProgram[FProgramIndex] := TMachineOperation.Create(cmOutput, 0, 0);
501        DebugSteps.AddStep(I - 1, FProgramIndex, soNormal);
502      end;
503      '[': begin
504        FProgram[FProgramIndex] := TMachineOperation.Create(cmLoopStart, 0, 0);
505        DebugSteps.AddStep(I - 1, FProgramIndex, soStepIn);
506      end;
507      ']': begin
508        FProgram[FProgramIndex] := TMachineOperation.Create(cmLoopEnd, 0 ,0);
509        DebugSteps.AddStep(I - 1, FProgramIndex, soStepOut);
510      end
511      else Dec(FProgramIndex);
512    end;
513    Inc(FProgramIndex);
514  end;
515  SetLength(FProgram, FProgramIndex);
516end;
517
518constructor TBFTarget.Create;
519begin
520  inherited Create;
521  MemorySize := 30000;
522  CellSize := 256;
523end;
524
525procedure TBFTarget.OptimizeSource;
526var
527  OldLength: Integer;
528begin
529  inherited;
530  if Optimizations.AddSub then OptimizeAddSub;
531  if Optimizations.Merge then
532  repeat
533    OldLength := Length(FProgram);
534    OptimizeMerge;
535  until Length(FProgram) = OldLength;
536  OptimizeZeroInitMemory;
537  if Optimizations.RelativeIndexes then OptimizeRelativeIndexes;
538  if Optimizations.CopyMultiply then OptimizeCopyMultiply;
539end;
540
541
542
543end.
544
Note: See TracBrowser for help on using the repository browser.