[TestCase(X64Register.R14)] // Never allocated in this method without a requirement
        public void Register_requirement_is_respected(X64Register required)
        {
            var method = new LowMethod <X64Register>();

            method.Locals.Add(new LowLocal <X64Register>(SimpleType.Bool));
            method.Locals.Add(new LowLocal <X64Register>(SimpleType.Bool, required));
            method.Blocks.Add(new LowBlock
            {
                Instructions =
                {
                    new LowInstruction(LowOp.LoadInt, 0, 0, 0, 1), // Load 1 -> #0
                    new LowInstruction(LowOp.Move,    1, 0, 0, 0), // Move #0 -> #1
                    new LowInstruction(LowOp.Compare, 0, 1, 0, 0), // Compare #1, #0
                    new LowInstruction(LowOp.Return,  0, 0, 0, 0)  // Return #0
                },
                Predecessors = Array.Empty <int>(),
                Successors   = Array.Empty <int>()
            });

            var(rewritten, allocationMap) = X64RegisterAllocator.Allocate(method);

            AssertDump(rewritten, @"
LB_0:
    LoadInt 0 0 1 -> 0
    Move 0 0 0 -> 1
    Compare 1 0 0 -> 0
    Return 0 0 0 -> 0");

            Assert.That(allocationMap.Get(0).localIndex, Is.EqualTo(0));
            Assert.That(allocationMap.Get(1).localIndex, Is.EqualTo(1));

            Assert.That(allocationMap.Get(0).location.IsSet, Is.True);
            Assert.That(allocationMap.Get(1).location.IsSet, Is.True);
            Assert.That(allocationMap.Get(1).location.Register, Is.EqualTo(required));
        }
Beispiel #2
0
        /// <summary>
        /// Emits native code for the given method.
        /// </summary>
        /// <param name="method">A compiled method in SSA form.</param>
        /// <param name="methodIndex">The compiler internal index for the method.</param>
        /// <param name="isEntryPoint">If true, this method is marked as the executable entry point.</param>
        /// <param name="dumpWriter">An optional text writer for debug dumping of the current method.</param>
        public void EmitMethod(CompiledMethod method, int methodIndex, bool isEntryPoint, TextWriter?dumpWriter)
        {
            _fixupsForMethod.Clear();
            _blockPositions.Clear();
            _savedRegisters.Clear();

            _peWriter.StartNewMethod(methodIndex, method.FullName);
            if (isEntryPoint)
            {
                // TODO: Emit a compiler-generated entry point that calls the user-defined entry point
                _peWriter.MarkEntryPoint();
            }

            // Lower the IR to a low-level form
            var loweredMethod = LoweringX64.Lower(method);

            // TODO: Does the LIR need some other optimization (block merging, etc.) before peephole?
            // Perform peephole optimization
            PeepholeOptimizer <X64Register> .Optimize(loweredMethod);

            // Debug log the lowering
            if (dumpWriter is object)
            {
                DebugLogBeforeAllocation(loweredMethod, method.FullName, dumpWriter);
            }

            // Allocate registers for locals (with special casing for parameters)
            var(allocatedMethod, allocationInfo) = X64RegisterAllocator.Allocate(loweredMethod);
            DetermineRegistersToSave(allocationInfo);

            if (dumpWriter is object)
            {
                DebugLogAfterAllocation(allocatedMethod, allocationInfo, dumpWriter);
            }

            // Emit the lowered IR
            for (var i = 0; i < allocatedMethod.Blocks.Count; i++)
            {
                _peWriter.Emitter.StartBlock(i, out var position);
                _blockPositions.Add(position);
                EmitBlock(i, allocatedMethod, allocationInfo, method);
            }

            // Apply fixups
            foreach (var fixup in _fixupsForMethod)
            {
                _peWriter.Emitter.ApplyFixup(fixup, _blockPositions[fixup.Tag]);
            }
        }
        public void Noncommutative_arithmetic_destination_is_not_same_as_right(LowOp op)
        {
            var method = new LowMethod <X64Register>();

            method.Locals.Add(new LowLocal <X64Register>(SimpleType.Int32));
            method.Locals.Add(new LowLocal <X64Register>(SimpleType.Int32));
            method.Locals.Add(new LowLocal <X64Register>(SimpleType.Int32));
            method.Locals.Add(new LowLocal <X64Register>(SimpleType.Int32, X64Register.Rax));
            method.Blocks.Add(new LowBlock
            {
                Instructions =
                {
                    new LowInstruction(LowOp.LoadInt, 0, 0, 0, 1), // Load 1 -> #0
                    new LowInstruction(LowOp.LoadInt, 1, 0, 0, 1), // Load 1 -> #1

                    new LowInstruction(op,            2, 0, 1, 0), // Subtract/Shift #0 - #1 -> #2

                    new LowInstruction(LowOp.Test,    0, 0, 0, 0), // Use #0
                    new LowInstruction(LowOp.Move,    3, 2, 0, 0), // Use #2
                    new LowInstruction(LowOp.Return,  0, 3, 0, 0)
                },
                Predecessors = Array.Empty <int>(),
                Successors   = Array.Empty <int>()
            });

            var(rewritten, allocationMap) = X64RegisterAllocator.Allocate(method);

            AssertDump(rewritten, $@"
LB_0:
    LoadInt 0 0 1 -> 0
    LoadInt 0 0 1 -> 1
    {op} 0 1 0 -> 2
    Test 0 0 0 -> 0
    Move 2 0 0 -> 3
    Return 3 0 0 -> 0");

            Assert.That(allocationMap.Get(0).localIndex, Is.EqualTo(0));
            Assert.That(allocationMap.Get(1).localIndex, Is.EqualTo(1));
            Assert.That(allocationMap.Get(2).localIndex, Is.EqualTo(2));

            // It would be tempting to assign #1 and #2 the same register, but that
            // is not good for x64: we would have to emit "mov r1, r0; sub r1, r1" where
            // local #1 is stored in r1 but local #2 lives there up until the last instruction.
            Assert.That(allocationMap.Get(1).location.Register,
                        Is.Not.EqualTo(allocationMap.Get(2).location.Register));
        }
        public void Intersecting_variables_in_single_block_have_separate_registers()
        {
            var method = new LowMethod <X64Register>();

            method.Locals.Add(new LowLocal <X64Register>(SimpleType.Bool));
            method.Locals.Add(new LowLocal <X64Register>(SimpleType.Bool));
            method.Locals.Add(new LowLocal <X64Register>(SimpleType.Void, X64Register.Rax));
            method.Blocks.Add(new LowBlock
            {
                Instructions =
                {
                    new LowInstruction(LowOp.LoadInt, 0, 0, 0, 1), // Load 1 -> #0
                    new LowInstruction(LowOp.LoadInt, 1, 0, 0, 1), // Load 1 -> #1
                    new LowInstruction(LowOp.Compare, 0, 0, 1, 0), // Compare #0, #1
                    new LowInstruction(LowOp.LoadInt, 2, 0, 0, 0), // Initialize void return
                    new LowInstruction(LowOp.Return,  0, 2, 0, 0)
                },
                Predecessors = Array.Empty <int>(),
                Successors   = Array.Empty <int>()
            });

            var(rewritten, allocationMap) = X64RegisterAllocator.Allocate(method);

            AssertDump(rewritten, @"
LB_0:
    LoadInt 0 0 1 -> 0
    LoadInt 0 0 1 -> 1
    Compare 0 1 0 -> 0
    LoadInt 0 0 0 -> 2
    Return 2 0 0 -> 0");

            Assert.That(allocationMap.Get(0).localIndex, Is.EqualTo(0));
            Assert.That(allocationMap.Get(1).localIndex, Is.EqualTo(1));
            Assert.That(allocationMap.Get(2).localIndex, Is.EqualTo(2));

            Assert.That(allocationMap.Get(0).location.IsSet, Is.True);
            Assert.That(allocationMap.Get(1).location.IsSet, Is.True);
            Assert.That(allocationMap.Get(0).location, Is.Not.EqualTo(allocationMap.Get(1).location));
            Assert.That(allocationMap.Get(2).location.Register, Is.EqualTo(X64Register.Rax));
        }
        public void Division_reserves_rdx(LowOp op)
        {
            var method = new LowMethod <X64Register>();

            method.Locals.Add(new LowLocal <X64Register>(SimpleType.Int32));
            method.Locals.Add(new LowLocal <X64Register>(SimpleType.Int32));
            method.Locals.Add(new LowLocal <X64Register>(SimpleType.Int32, X64Register.Rax));
            method.Locals.Add(new LowLocal <X64Register>(SimpleType.Int32, X64Register.Rax));
            method.Blocks.Add(new LowBlock
            {
                Instructions =
                {
                    new LowInstruction(LowOp.LoadInt, 0, 0, 0, 1), // Load 1 -> #0
                    new LowInstruction(LowOp.LoadInt, 1, 0, 0, 1), // Load 1 -> #1
                    new LowInstruction(LowOp.LoadInt, 2, 0, 0, 1), // Load 1 -> #2

                    new LowInstruction(op,            3, 2, 2, 0), // Divide #2 / #2 -> #3

                    new LowInstruction(LowOp.Test,    0, 0, 0, 0), // Use #0
                    new LowInstruction(LowOp.Move,    0, 1, 0, 0), // Use #1
                    new LowInstruction(LowOp.Return,  0, 3, 0, 0)
                },
                Predecessors = Array.Empty <int>(),
                Successors   = Array.Empty <int>()
            });

            var(_, allocationMap) = X64RegisterAllocator.Allocate(method);

            // RDX holds the upper part of dividend and therefore must be reserved
            for (var i = 0; i < allocationMap.IntervalCount; i++)
            {
                var(location, localIndex) = allocationMap.Get(i);
                if (localIndex >= 0)
                {
                    Assert.That(location.Register, Is.Not.EqualTo(X64Register.Rdx));
                }
            }
        }
        public void Single_phi_in_a_loop()
        {
            var method = new LowMethod <X64Register>();

            method.Locals.Add(new LowLocal <X64Register>(SimpleType.Int32));
            method.Locals.Add(new LowLocal <X64Register>(SimpleType.Int32));
            method.Locals.Add(new LowLocal <X64Register>(SimpleType.Int32));
            method.Blocks.Add(new LowBlock
            {
                Instructions =
                {
                    new LowInstruction(LowOp.LoadInt,    1, 0, 0, 1), // Load 1 -> #1
                    new LowInstruction(LowOp.IntegerAdd, 2, 0, 1, 0), // Add #0, #1 -> #2
                    new LowInstruction(LowOp.Jump,       0, 0, 0, 0)
                },
                Phis         = new[] { new Phi(0, ImmutableList <int> .Empty.Add(2)) },
                Predecessors = new int[] { 0 },
                Successors   = new int[] { 0 }
            });

            var(rewritten, allocationMap) = X64RegisterAllocator.Allocate(method);

            // #0 and #2 have the same register, therefore there is no move instruction
            AssertDump(rewritten, @"
LB_0:
    LoadInt 0 0 1 -> 1
    IntegerAdd 0 1 0 -> 2
    Jump 0 0 0 -> 0");

            Assert.That(allocationMap.Get(0).localIndex, Is.EqualTo(0));
            Assert.That(allocationMap.Get(1).localIndex, Is.EqualTo(1));

            Assert.That(allocationMap.Get(0).location.IsSet, Is.True);
            Assert.That(allocationMap.Get(1).location.IsSet, Is.True);
            Assert.That(allocationMap.Get(0).location, Is.EqualTo(allocationMap.Get(2).location));
            Assert.That(allocationMap.Get(0).location, Is.Not.EqualTo(allocationMap.Get(1).location));
        }
        public void Call_instruction_reserves_registers(LowOp callOp)
        {
            var method = new LowMethod <X64Register>();

            method.Locals.Add(new LowLocal <X64Register>(SimpleType.Bool));
            method.Locals.Add(new LowLocal <X64Register>(SimpleType.Bool));
            method.Locals.Add(new LowLocal <X64Register>(SimpleType.Bool));
            method.Locals.Add(new LowLocal <X64Register>(SimpleType.Bool));
            method.Locals.Add(new LowLocal <X64Register>(SimpleType.Bool));
            method.Locals.Add(new LowLocal <X64Register>(SimpleType.Void, X64Register.Rax));
            method.Blocks.Add(new LowBlock
            {
                Instructions =
                {
                    new LowInstruction(LowOp.LoadInt, 0, 0, 0, 1),    // Load 1 -> #0
                    new LowInstruction(LowOp.LoadInt, 1, 0, 0, 1),    // Load 1 -> #1
                    new LowInstruction(LowOp.LoadInt, 2, 0, 0, 1),    // Load 1 -> #2
                    new LowInstruction(LowOp.LoadInt, 3, 0, 0, 1),    // Load 1 -> #3
                    new LowInstruction(LowOp.LoadInt, 4, 0, 0, 1),    // Load 1 -> #4

                    new LowInstruction(callOp,        5, 0, 0, 1234), // Call - this trashes rax, rcx, rdx, r8 and r9

                    new LowInstruction(LowOp.Test,    0, 0, 0, 0),    // Test #0
                    new LowInstruction(LowOp.Test,    0, 1, 0, 0),    // Test #1
                    new LowInstruction(LowOp.Test,    0, 2, 0, 0),    // Test #2
                    new LowInstruction(LowOp.Test,    0, 3, 0, 0),    // Test #3
                    new LowInstruction(LowOp.Test,    0, 4, 0, 0),    // Test #4
                    new LowInstruction(LowOp.Return,  5, 0, 0, 0)
                },
                Predecessors = Array.Empty <int>(),
                Successors   = Array.Empty <int>()
            });

            var(_, allocationMap) = X64RegisterAllocator.Allocate(method);

            // No local variable should be assigned to a blocked register
            for (var i = 0; i < allocationMap.IntervalCount; i++)
            {
                var(location, localIndex) = allocationMap.Get(i);

                if (localIndex == -1)
                {
                    continue;
                }

                if (localIndex == 5)
                {
                    Assert.That(location.Register, Is.EqualTo(X64Register.Rax));
                    continue;
                }

                Assert.That(location.IsSet, Is.True);
                Assert.That(location.Register, Is.Not.EqualTo(X64Register.Rax));
                Assert.That(location.Register, Is.Not.EqualTo(X64Register.Rcx));
                Assert.That(location.Register, Is.Not.EqualTo(X64Register.Rdx));
                Assert.That(location.Register, Is.Not.EqualTo(X64Register.R8));
                Assert.That(location.Register, Is.Not.EqualTo(X64Register.R9));
                Assert.That(location.Register, Is.Not.EqualTo(X64Register.R10));
                Assert.That(location.Register, Is.Not.EqualTo(X64Register.R11));

                // ..and as a general sanity check, do not allocate the stack pointer!
                Assert.That(location.Register, Is.Not.EqualTo(X64Register.Rsp));
            }
        }
        public void Complex_phi_chain_is_not_allocated_the_same_register()
        {
            // void Swap() {
            //   int32 a = 10;
            //   int32 b = 11;
            //   while (a < b) {
            //      int32 temp = a;
            //      a = b;
            //      b = temp;
            //   }
            // }
            var method = new LowMethod <X64Register>();

            method.Locals.Add(new LowLocal <X64Register>(SimpleType.Int32));
            method.Locals.Add(new LowLocal <X64Register>(SimpleType.Int32));
            method.Locals.Add(new LowLocal <X64Register>(SimpleType.Int32));
            method.Locals.Add(new LowLocal <X64Register>(SimpleType.Int32));
            method.Locals.Add(new LowLocal <X64Register>(SimpleType.Void, X64Register.Rax));

            method.Blocks.Add(new LowBlock
            {
                Instructions =
                {
                    new LowInstruction(LowOp.LoadInt, 0, 0, 0, 10), // Load 10 -> #0
                    new LowInstruction(LowOp.LoadInt, 1, 0, 0, 11), // Load 11 -> #1
                    new LowInstruction(LowOp.Jump,    1, 0, 0, 0)
                },
                Predecessors = Array.Empty <int>(),
                Successors   = new[] { 1 },
            });
            method.Blocks.Add(new LowBlock
            {
                Phis = new List <Phi>()
                {
                    new Phi(2, new[] { 0, 3 }.ToImmutableList()),
                    new Phi(3, new[] { 1, 2 }.ToImmutableList())
                },
                Instructions =
                {
                    new LowInstruction(LowOp.Compare,    0, 2, 3, 0), // Compare #2, #3
                    new LowInstruction(LowOp.JumpIfLess, 3, 0, 0, 0), // JumpIfLess LB_3
                    new LowInstruction(LowOp.Jump,       2, 0, 0, 0), // Jump LB_2
                },
                Predecessors = new[] { 0, 2 },
                Successors   = new[] { 3, 2 },
            });
            method.Blocks.Add(new LowBlock
            {
                Instructions =
                {
                    new LowInstruction(LowOp.Jump, 1, 0, 0, 0) // Jump LB_1
                },
                Predecessors = new[] { 1 },
                Successors   = new[] { 1 },
            });
            method.Blocks.Add(new LowBlock
            {
                Instructions =
                {
                    new LowInstruction(LowOp.LoadInt, 4, 0, 0, 0), // Void return
                    new LowInstruction(LowOp.Return,  0, 4, 0, 0)  // Return
                },
                Predecessors = new[] { 1 },
                Successors   = Array.Empty <int>(),
            });

            // Act
            var(converted, map) = X64RegisterAllocator.Allocate(method);

            // Assert
            AssertDump(converted, @"
LB_0:
    LoadInt 0 0 10 -> 0
    LoadInt 0 0 11 -> 1
    Jump 0 0 0 -> 1
LB_1:
    Compare 2 3 0 -> 0
    JumpIfLess 0 0 0 -> 3
    Jump 0 0 0 -> 2
LB_2:
    Swap 3 2 0 -> 0
    Jump 0 0 0 -> 1
LB_3:
    LoadInt 0 0 0 -> 4
    Return 4 0 0 -> 0");

            // The Phi destinations must have intersecting intervals and therefore different registers,
            // while the values from the initial block should not need any copies
            Assert.That(map.Get(3).location, Is.Not.EqualTo(map.Get(4).location));
            Assert.That(map.Get(0).location, Is.EqualTo(map.Get(2).location));
            Assert.That(map.Get(1).location, Is.EqualTo(map.Get(3).location));
        }