diff --git a/kerboscript_tests/integration/constantPropagation.ks b/kerboscript_tests/integration/constantPropagation.ks new file mode 100644 index 0000000000..a44ed81066 --- /dev/null +++ b/kerboscript_tests/integration/constantPropagation.ks @@ -0,0 +1,63 @@ +@lazyGlobal OFF. + +global a is 5. +global _false is false. + +local b is 2. +local c is 4. +local d is 7. +local h is false. + +print("test"). // The assignments should happen after this line. + +print(b+c). // 6 +print(h). // False + +global function func { + print(b+c). // 6 + print(b+d). // 9 + print(b+a). // 7 + set h to true. +} + +func(). + +{ + local i is 10. + print(i). // 10 + print(i+c). // 14 +} + +print(h). // True + +set h to false. + +print(h). // False + +lock e to a + b. + +print(e). // 7 +set b to 1. +print(e). // 6 + +lock g to c + d. +print(g). // 11 +set d to 5. +print(g). // 9 + +when _false then { + set c to 10. +} + +print(b+c). // 5 + +set b to 1. +set b to b + d. + +print(b). // 6 + +until b = 1 { + set b to b - 1. +} + +print(b). // 1 diff --git a/kerboscript_tests/integration/operators.ks b/kerboscript_tests/integration/operators.ks index c4aa36b732..a68640d512 100644 --- a/kerboscript_tests/integration/operators.ks +++ b/kerboscript_tests/integration/operators.ks @@ -18,3 +18,8 @@ print(1 = 1). print(1 <> 2). print(true and true). print(true or false). +print("A" + "b"). +print(a * 0). +print(a ^ 0). +print(arcTan2(0,1)). +print(abs(-1)). diff --git a/kerboscript_tests/integration/operators_invalid.ks b/kerboscript_tests/integration/operators_invalid.ks new file mode 100644 index 0000000000..75473a709a --- /dev/null +++ b/kerboscript_tests/integration/operators_invalid.ks @@ -0,0 +1,21 @@ + +set a to 0. + +print(+ 1). +print(- (-1)). +print(not false). +print(defined a). + +print(1 + 2). +print(2 * 3). +print(4 / 2). +print(3 ^ 2). +print(2 > 1). +print(2 >= 1). +print(1 < 2). +print(1 <= 2). +print(1 = 1). +print(1 <> 2). +print(true and true). +print(true or false). +print(1 + false). diff --git a/kerboscript_tests/integration/peepholeOptimizations.ks b/kerboscript_tests/integration/peepholeOptimizations.ks new file mode 100644 index 0000000000..b6ef105a92 --- /dev/null +++ b/kerboscript_tests/integration/peepholeOptimizations.ks @@ -0,0 +1,51 @@ +parameter a is TRUE. +parameter c is 3. +parameter x is 2. +parameter z is 5. + +print("test":typename()). +// v() is unavailable in unit testing. +//local v1 is v(1,2,3). +//local v2 is v(3,4,5). +// Multiplying scalars will work equally well. +local v1 is 2. +local v2 is 3. + +print(vectorDotProduct(v1,v2)). // =6 +print(vdot(v1,v2)). // =6 + +if not a { + print("Don't print this."). +} + +if not (not a) { + print("Print this."). +} + +print(c*x+c*z). // = 21 +print(c*x+z*c). // = 21 +print(x*c+c*z). // = 21 +print(x*c+z*c). // = 21 + +print(-x+z). // = 3 +print(x+-z). // = -3 + +print(x--z). // = 7 + +print(x^z/x). // = 16 +print(z^4/z). // = 125 + +print(x^z*x). // = 64 +print(z^4*z). // = 3125 + +print(x^z*x^c). // = 256 + +print(x^z/x^c). // = 4 + +print(z^3/z). // = 25 + +print(z*z*z). // = 125 + +print(z^3/z*z). // = 125 + +print(x^2*x^z/x*x^3). // = 512 \ No newline at end of file diff --git a/kerboscript_tests/integration/suffixReplacement.ks b/kerboscript_tests/integration/suffixReplacement.ks new file mode 100644 index 0000000000..2e93f6cd13 --- /dev/null +++ b/kerboscript_tests/integration/suffixReplacement.ks @@ -0,0 +1,5 @@ +@CLOBBERBUILTINS OFF. +set airspeed to 100. // Clobber the built-in so that this value is pulled after replacement. +print(CONSTANT:g0). +print(CONSTANT():pi). +print(SHIP:airspeed). \ No newline at end of file diff --git a/src/kOS.Safe.Test/Execution/BaseIntegrationTest.cs b/src/kOS.Safe.Test/Execution/BaseIntegrationTest.cs index 615e99466e..1731e16ed0 100644 --- a/src/kOS.Safe.Test/Execution/BaseIntegrationTest.cs +++ b/src/kOS.Safe.Test/Execution/BaseIntegrationTest.cs @@ -39,6 +39,7 @@ public abstract class BaseIntegrationTest private SafeSharedObjects shared; private Screen screen; private string baseDir; + protected virtual OptimizationLevel OptimizationLevel => OptimizationLevel.None; private string FindKerboscriptTests() { @@ -86,12 +87,14 @@ protected void RunScript(string fileName) IsCalledFromRun = false, FuncManager = shared.FunctionManager, BindManager = shared.BindingMgr, - AllowClobberBuiltins = SafeHouse.Config.AllowClobberBuiltIns + AllowClobberBuiltins = SafeHouse.Config.AllowClobberBuiltIns, + OptimizationLevel = OptimizationLevel }); cpu.Boot(); screen.ClearOutput(); + cpu.PushArgumentStack(new KOSArgMarkerType()); cpu.GetCurrentContext().AddParts(compiled); } diff --git a/src/kOS.Safe.Test/Execution/OptimizationTest.cs b/src/kOS.Safe.Test/Execution/OptimizationTest.cs new file mode 100644 index 0000000000..4216b0a5fc --- /dev/null +++ b/src/kOS.Safe.Test/Execution/OptimizationTest.cs @@ -0,0 +1,210 @@ +using System; +using NUnit.Framework; +using kOS.Safe.Compilation; +using kOS.Safe.Exceptions; + +namespace kOS.Safe.Test.Execution +{ + [TestFixture] + public class OptimizationTest : BaseIntegrationTest + { + protected override OptimizationLevel OptimizationLevel => OptimizationLevel.Minimal; + + [Test] + public void TestBasic() + { + // Tests that the bare minimum works + + RunScript("integration/basic.ks"); + RunSingleStep(); + AssertOutput( + "text" + ); + } + + [Test] + public void TestVars() + { + // Tests that basic variable assignment and reference works + + RunScript("integration/vars.ks"); + RunSingleStep(); + AssertOutput( + "1", + "2", + "3" + ); + } + + [Test] + public void TestFunc() + { + // Tests that basic no-args function calls work + + RunScript("integration/func.ks"); + RunSingleStep(); + AssertOutput( + "a" + ); + } + + [Test] + public void TestFuncArgs() + { + // Tests that explicit and default function parameters work + + RunScript("integration/func_args.ks"); + RunSingleStep(); + AssertOutput( + "0", + "1", + "2", + "3" + ); + } + + [Test] + public void TestOperators() + { + // Test that all the basic operators work + + RunScript("integration/operators.ks"); + RunSingleStep(); + AssertOutput( + "1", + "1", + "True", + "True", + "3", + "6", + "2", + "9", + "True", + "True", + "True", + "True", + "True", + "True", + "True", + "True", + "Ab", + "0", + "1", + "0", + "1" + ); + } + + [Test] + [ExpectedException(typeof(KOSCompileException))] + public void TestOperatorsException() + { + // Test that an invalid operation throws a compile error during constant folding + RunScript("integration/operators_invalid.ks"); + RunSingleStep(); + } + + [Test] + public void TestLock() + { + // Test that locks in the same file works + RunScript("integration/lock.ks"); + RunSingleStep(); + AssertOutput( + "3", + "4", + "5" + ); + } + + [Test] + public void TestSuffixes() + { + // Test that various suffix and index combinations work for getting and setting + RunScript("integration/suffixes.ks"); + RunSingleStep(); + RunSingleStep(); + AssertOutput( + "0", + "1", + "2", + "3", + "0", + "False" + ); + } + + [Test] + public void TestSuffixReplacement() + { + // Test that certain suffixes are replaced + RunScript("integration/suffixReplacement.ks"); + RunSingleStep(); + AssertOutput( + "9.80665", + "3.14159265358979", + "100" + ); + } + + + [Test] + public void TestPeepholeOptimizations() + { + // Test that certain suffixes are replaced + RunScript("integration/peepholeOptimizations.ks"); + RunSingleStep(); + AssertOutput( + "String", + "6", + "6", + "Print this.", + "21", + "21", + "21", + "21", + "3", + "-3", + "7", + "16", + "125", + "64", + "3125", + "256", + "4", + "25", + "125", + "125", + "512" + ); + } + + + [Test] + public void TestConstantPropagation() + { + // Test that local constant variables propagate + RunScript("integration/constantPropagation.ks"); + RunSingleStep(); + AssertOutput( + "test", + "6", + "False", + "6", + "9", + "7", + "10", + "14", + "True", + "False", + "7", + "6", + "11", + "9", + "5", + "6", + "1" + ); + } + } +} diff --git a/src/kOS.Safe.Test/Execution/SimpleTest.cs b/src/kOS.Safe.Test/Execution/SimpleTest.cs index 5e8a7309a7..0615749a52 100644 --- a/src/kOS.Safe.Test/Execution/SimpleTest.cs +++ b/src/kOS.Safe.Test/Execution/SimpleTest.cs @@ -83,7 +83,12 @@ public void TestOperators() "True", "True", "True", - "True" + "True", + "Ab", + "0", + "1", + "0", + "1" ); } diff --git a/src/kOS.Safe/Compilation/CompilerOptions.cs b/src/kOS.Safe/Compilation/CompilerOptions.cs index 9c16e16b62..40714786f0 100644 --- a/src/kOS.Safe/Compilation/CompilerOptions.cs +++ b/src/kOS.Safe/Compilation/CompilerOptions.cs @@ -18,6 +18,7 @@ public class CompilerOptions public bool AllowClobberBuiltins { get; set; } public IFunctionManager FuncManager { get; set; } public IBindingManager BindManager { get; set; } + public OptimizationLevel OptimizationLevel { get; set; } public CompilerOptions() { LoadDefaults(); @@ -30,6 +31,7 @@ private void LoadDefaults() BindManager = null; IsCalledFromRun = true; AllowClobberBuiltins = false; + OptimizationLevel = OptimizationLevel.Balanced; } public bool BuiltInFunctionExists(string identifier) diff --git a/src/kOS.Safe/Compilation/IR/BasicBlock.cs b/src/kOS.Safe/Compilation/IR/BasicBlock.cs new file mode 100644 index 0000000000..b3d6cec958 --- /dev/null +++ b/src/kOS.Safe/Compilation/IR/BasicBlock.cs @@ -0,0 +1,237 @@ +using System.Collections.Generic; +using System.Linq; +using kOS.Safe.Compilation.Optimization; + +namespace kOS.Safe.Compilation.IR +{ + public class BasicBlock + { + private readonly HashSet predecessors = new HashSet(); + private readonly HashSet successors = new HashSet(); + private BasicBlock dominator; + private readonly HashSet dominates = new HashSet(); + private readonly List parameters = new List(); + private readonly Stack exitStackState = new Stack(); // Note that this is reversed from the real stack. Just now we don't reverse it four times. + private readonly string nonSequentialLabel = null; + private IRScope scope; + + public IRScope Scope + { + get => scope; + set + { + scope?.RemoveBlock(this); + scope = value; + scope.EnrollBlock(this); + } + } + public int StartIndex { get; } + public int EndIndex { get; } + public List Instructions { get; } = new List(); + public IReadOnlyCollection Successors => successors; + public IReadOnlyCollection Predecessors => predecessors; + public string Label => nonSequentialLabel ?? $"@BB#{ID}"; + public int ID { get; } + public BasicBlock Dominator + { + get => dominator; + protected set + { + dominator?.dominates.Remove(this); + dominator = value; + dominator?.dominates.Add(this); + } + } + public IReadOnlyCollection Dominates => dominates; + public ExtendedBasicBlock ExtendedBlock { get; set; } + public IRJump FallthroughJump { get; set; } = null; +#if DEBUG + internal Opcode[] OriginalOpcodes { get; set; } + internal Opcode[] GeneratedOpcodes => EmitOpCodes().ToArray(); +#endif + + public BasicBlock(int startIndex, int endIndex, int id, string nonSequentialLabel = null) + { + StartIndex = startIndex; + EndIndex = endIndex; + ID = id; + this.nonSequentialLabel = nonSequentialLabel; + } + + public void Add(IRInstruction instruction) + => Instructions.Add(instruction); + + public void AddSuccessor(BasicBlock successor) + { + successors.Add(successor); + successor.AddPredecessor(this); + } + protected void AddPredecessor(BasicBlock predecessor) + { + predecessors.Add(predecessor); + } + public void RemoveSuccessor(BasicBlock successor) + { + if (!successors.Remove(successor)) + throw new System.ArgumentException(nameof(successor)); + successor.predecessors.Remove(this); + if (successor.predecessors.Count == 0) + successor.Dominator = null; + else + successor.Dominator.EstablishDominance(); + } + public void EstablishDominance() + { + // Compute reverse postorder + var postorder = new List(); + var visited = new HashSet(); + DepthFirstSearch(this, visited, postorder); + + postorder.Reverse(); + + // Map block to index + Dictionary index = new Dictionary(); + for (int i = 0; i < postorder.Count; i++) + index[postorder[i]] = i; + + // Initialize + postorder.Remove(this); + bool changed = true; + while (changed) + { + changed = false; + + foreach (BasicBlock block in postorder) + { + // Pick first predecessor with defined dominator + BasicBlock newIdom = block.predecessors.Where(p => p != block).FirstOrDefault + (p => p == this || p.Dominator != null); + + if (newIdom == null) + continue; + + foreach (BasicBlock predecessor in block.predecessors) + { + if (predecessor == newIdom) + continue; + + if (predecessor.Dominator != null) + newIdom = Intersect(predecessor, newIdom, index); + } + + if (block.Dominator != newIdom) + { + block.Dominator = newIdom; + changed = true; + } + } + } + } + private static BasicBlock Intersect(BasicBlock b1, BasicBlock b2, Dictionary index) + { + while (b1 != b2) + { + while (index[b1] > index[b2]) + b1 = b1.Dominator; + + while (index[b2] > index[b1]) + b2 = b2.Dominator; + } + + return b1; + } + private static void DepthFirstSearch(BasicBlock block, HashSet visited, List postorder) + { + if (!visited.Add(block)) + return; + + foreach (BasicBlock successor in block.successors) + DepthFirstSearch(successor, visited, postorder); + + postorder.Add(block); + } + + public void AddParameter(IRParameter parameter) + { + parameters.Add(parameter); + } + public void StoreLocalVariable(IRVariableBase variable) + => Scope.StoreLocalVariable(variable); + public void StoreGlobalVariable(IRVariableBase variable) + => Scope.StoreGlobalVariable(variable); + public void StoreVariable(IRVariableBase variable) + => Scope.StoreVariable(variable); + public bool TryStoreVariable(IRVariableBase variable) + => Scope.TryStoreVariable(variable); + public IRVariableBase PushVariable(string name, Opcode opcode) + { + IRScope globalScope = Scope.GetGlobalScope(); + IRVariableBase result = Scope.GetVariableNamed(name); + if (result == null) + { + result = new IRVariable(name, globalScope, opcode); + Scope.StoreGlobalVariable(result); + } + return result; + } + public IRScope GetScopeForVariableNamed(string name) + => Scope.GetScopeForVariableNamed(name); + + public void SetStackState(Stack stack) + { + while (stack.Count > 0) + exitStackState.Push(stack.Pop()); + } + + public override string ToString() + { + return $"BasicBlock#{ID}: {StartIndex}-{EndIndex}; {Instructions.Count} Instructions"; + } + + public IEnumerable EmitOpCodes() + { + bool addedFallthrough = FallthroughJump != null && Instructions.LastOrDefault() == FallthroughJump; + if (addedFallthrough) + Instructions.Add(FallthroughJump); + bool first = true; + foreach (IRInstruction instruction in Instructions.Take(Instructions.Count - 1)) + { + foreach (Opcode opcode in instruction.EmitOpcode()) + { + if (first) + { + opcode.Label = Label; + first = false; + } + yield return opcode; + } + } + foreach (IRValue stackValue in exitStackState) + { + foreach (Opcode opcode in stackValue.EmitPush()) + { + if (first) + { + opcode.Label = Label; + first = false; + } + yield return opcode; + } + } + if (Instructions.Any()) + { + foreach (Opcode opcode in Instructions.Last().EmitOpcode()) + { + if (first) + { + opcode.Label = Label; + first = false; + } + yield return opcode; + } + } + if (addedFallthrough) + Instructions.Remove(FallthroughJump); + } + } +} diff --git a/src/kOS.Safe/Compilation/IR/IOperandInstructionBase.cs b/src/kOS.Safe/Compilation/IR/IOperandInstructionBase.cs new file mode 100644 index 0000000000..ed1aa7251f --- /dev/null +++ b/src/kOS.Safe/Compilation/IR/IOperandInstructionBase.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; + +namespace kOS.Safe.Compilation.IR +{ + public interface IOperandInstructionBase + { } + public interface ISingleOperandInstruction : IOperandInstructionBase + { + IRValue Operand { get; set; } + } + public interface IMultipleOperandInstruction : IOperandInstructionBase + { + IEnumerable Operands { get; } + IRValue this[int index] { get; set; } + int OperandCount { get; } + } +} diff --git a/src/kOS.Safe/Compilation/IR/IRBuilder.cs b/src/kOS.Safe/Compilation/IR/IRBuilder.cs new file mode 100644 index 0000000000..5951b460fc --- /dev/null +++ b/src/kOS.Safe/Compilation/IR/IRBuilder.cs @@ -0,0 +1,361 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace kOS.Safe.Compilation.IR +{ + public class IRBuilder + { + private int nextTempId = 0; + private int blockID = 0; + + public List Lower(List code) + { + List blocks = new List(); + if (code.Count == 0) + return blocks; + Dictionary labels = ProgramBuilder.MapLabels(code); + CreateBlocks(code, labels, blocks); + FillBlocks(code, labels, blocks); + return blocks; + } + + private void CreateBlocks(List code, Dictionary labels, List blocks) + { + IRScope globalScope = new IRScope(null) { IsGlobalScope = true }; + SortedSet leaders = new SortedSet() { 0 }; + HashSet scopePushes = new HashSet(); + HashSet scopePops = new HashSet(); + for (int i = 0; i < code.Count; i++) + { + if (code[i] is BranchOpcode branch) + { + leaders.Add(i + 1); + if (branch.DestinationLabel != string.Empty) + leaders.Add(labels[branch.DestinationLabel]); + else + leaders.Add(i + branch.Distance); + } + else if (code[i] is OpcodeJumpStack) + { + throw new NotImplementedException("OpcodeJumpStack is not implemented for optimization because it is non-deterministic. Use OptimizationLevel.None."); + } + else if (code[i] is OpcodeReturn ret) + { + leaders.Add(i + 1); + if (ret.Depth > 0) + scopePops.Add(i); + } + else if (code[i] is OpcodePushScope) + { + leaders.Add(i); + scopePushes.Add(i); + } + else if (code[i] is OpcodePopScope) + { + leaders.Add(i + 1); + scopePops.Add(i); + } + } + leaders.Add(code.Count); + foreach (int startIndex in leaders.Take(leaders.Count - 1)) + { + int endIndex = leaders.First(i => i > startIndex) - 1; + string label = code[startIndex].Label; + if (label.StartsWith("@")) + label = null; + BasicBlock block = new BasicBlock(startIndex, endIndex, blockID++, label); + + blocks.Add(block); + } + foreach (BasicBlock block in blocks) + { + Opcode lastOpcode = code[block.EndIndex]; + if (lastOpcode is BranchOpcode branch) + { + int destinationIndex = branch.DestinationLabel != string.Empty ? labels[branch.DestinationLabel] : block.EndIndex + branch.Distance; + block.AddSuccessor(GetBlockFromStartIndex(blocks, destinationIndex)); + if (!(branch is OpcodeBranchJump)) + block.AddSuccessor(GetBlockFromStartIndex(blocks, block.EndIndex + 1)); + } + else if (blocks.Any(b => b.StartIndex == block.EndIndex + 1)) + { + BasicBlock successor = GetBlockFromStartIndex(blocks, block.EndIndex + 1); + block.FallthroughJump = new IRJump(successor, lastOpcode.SourceLine, lastOpcode.SourceColumn); + block.AddSuccessor(successor); + } +#if DEBUG + block.OriginalOpcodes = code.ToArray(); +#endif + } + + BasicBlock rootBlock = GetBlockFromStartIndex(blocks, 0); + rootBlock.EstablishDominance(); + + AssignScopes(rootBlock, globalScope, scopePushes, scopePops); + } + + private BasicBlock GetBlockFromStartIndex(List blocks, int startIndex) + => blocks.First(b => b.StartIndex == startIndex); + + private static void AssignScopes(BasicBlock root, IRScope globalScope, HashSet scopePushIndices, HashSet scopePopIndices) + { + Stack scopeStack = new Stack(); + scopeStack.Push(globalScope); + + void Visit(BasicBlock block) + { + if (scopePushIndices.Contains(block.StartIndex)) + scopeStack.Push( + new IRScope(scopeStack.Peek()) + { + HeaderBlock = block + }); + + block.Scope = scopeStack.Peek(); + + // Return statements can pop multiple scopes + if (scopePopIndices.Contains(block.EndIndex)) + scopeStack.Pop().FooterBlock = block; + + foreach (BasicBlock child in block.Dominates) + Visit(child); + } + + Visit(root); + } + + private void FillBlocks(List code, Dictionary labels, List blocks) + { + Stack stack = new Stack(); + BasicBlock currentBlock = GetBlockFromStartIndex(blocks, 0); + for (int i = 0; i < code.Count; i++) + { + if (i > currentBlock.EndIndex) + { + currentBlock.SetStackState(stack); + currentBlock = GetBlockFromStartIndex(blocks, i); + } + ParseInstruction(code[i], currentBlock, stack, labels, i, blocks); + } + } + + private IRTemp CreateTemp() + { + IRTemp result = new IRTemp(nextTempId); + nextTempId++; + return result; + } + private static IRValue PopFromStack(Stack stack, BasicBlock block) + { + if (stack.Count > 0) + return stack.Pop(); + IRParameter parameter = new IRParameter(); + block.AddParameter(parameter); + return parameter; + } + + private void ParseInstruction(Opcode opcode, BasicBlock currentBlock, Stack stack, Dictionary labels, int index, List blocks) + { + IRValue PopStack() => PopFromStack(stack, currentBlock); + switch (opcode) + { + case OpcodeStore store: + IRScope scope = currentBlock.GetScopeForVariableNamed(store.Identifier); + IRVariable variable = new IRVariable(store, scope); + IRAssign assignment = new IRAssign(store, variable, PopStack()) { Scope = IRAssign.StoreScope.Ambivalent }; + currentBlock.StoreVariable(variable); + currentBlock.Add(assignment); + break; + case OpcodeStoreExist storeExist: + scope = currentBlock.GetScopeForVariableNamed(storeExist.Identifier); + variable = new IRVariable(storeExist, scope); + assignment = new IRAssign(storeExist, variable, PopStack()) { AssertExists = true }; + if (scope.IsGlobalScope) + currentBlock.StoreGlobalVariable(variable); + else if (!currentBlock.TryStoreVariable(variable)) + throw new Exceptions.KOSCompileException(new KS.LineCol(assignment.SourceLine, assignment.SourceColumn), "Assert that variable exists failed."); + currentBlock.Add(assignment); + break; + case OpcodeStoreLocal storeLocal: + scope = currentBlock.Scope; + variable = new IRVariable(storeLocal, scope); + assignment = new IRAssign(storeLocal, variable, PopStack()) { Scope = IRAssign.StoreScope.Local }; + currentBlock.StoreLocalVariable(variable); + currentBlock.Add(assignment); + break; + case OpcodeStoreGlobal storeGlobal: + scope = currentBlock.Scope.GetGlobalScope(); + if (!scope.IsGlobalScope) + throw new Exceptions.KOSYouShouldNeverSeeThisException("Tried to store a variable in global scope when it already exists locally."); + variable = new IRVariable(storeGlobal, scope); + assignment = new IRAssign(storeGlobal, variable, PopStack()) { Scope = IRAssign.StoreScope.Global }; + currentBlock.StoreGlobalVariable(variable); + currentBlock.Add(assignment); + break; + case OpcodeExists exists: + IRTemp temp = CreateTemp(); + IRInstruction instruction = new IRUnaryOp(temp, exists, PopStack()); + temp.Parent = instruction; + //currentBlock.Add(instruction); + stack.Push(temp); + break; + case OpcodeUnset unset: + currentBlock.Add(new IRUnaryConsumer(unset, PopStack(), true)); + break; + case OpcodeGetMethod getMethod: + temp = CreateTemp(); + instruction = new IRSuffixGetMethod(temp, PopStack(), getMethod); + //currentBlock.Add(instruction); + temp.Parent = instruction; + stack.Push(temp); + break; + case OpcodeGetMember getMember: + temp = CreateTemp(); + instruction = new IRSuffixGet(temp, PopStack(), getMember); + temp.Parent = instruction; + //currentBlock.Add(instruction); + stack.Push(temp); + break; + case OpcodeSetMember setMember: + IRValue value = PopStack(); + IRValue memberObj = PopStack(); + currentBlock.Add(new IRSuffixSet(memberObj, value, setMember)); + break; + case OpcodeGetIndex getIndex: + IRValue targetIndex = PopStack(); + IRValue indexObj = PopStack(); + temp = CreateTemp(); + instruction = new IRIndexGet(temp, indexObj, targetIndex, getIndex); + //currentBlock.Add(instruction); + temp.Parent = instruction; + stack.Push(temp); + break; + case OpcodeSetIndex setIndex: + value = PopStack(); + targetIndex = PopStack(); + indexObj = PopStack(); + currentBlock.Add(new IRIndexSet(indexObj, targetIndex, value, setIndex)); + break; + case OpcodeEOF _: + case OpcodeEOP _: + case OpcodeNOP _: + case OpcodeBogus _: + case OpcodePushScope _: + case OpcodePopScope _: + case OpcodeArgBottom _: + currentBlock.Add(new IRNoStackInstruction(opcode)); + break; + case OpcodeTestArgBottom _: + temp = CreateTemp(); + instruction = new IRNonVarPush(temp, opcode); + temp.Parent = instruction; + //currentBlock.Add(instruction); + stack.Push(temp); + break; + case OpcodeBranchIfTrue branchIfTrue: + int target; + if (string.IsNullOrEmpty(branchIfTrue.DestinationLabel)) + target = index + branchIfTrue.Distance; + else + target = labels[branchIfTrue.DestinationLabel]; + currentBlock.Add(new IRBranch(PopStack(), + GetBlockFromStartIndex(blocks, target), + GetBlockFromStartIndex(blocks, currentBlock.EndIndex + 1), + branchIfTrue)); + break; + case OpcodeBranchIfFalse branchIfFalse: + if (string.IsNullOrEmpty(branchIfFalse.DestinationLabel)) + target = index + branchIfFalse.Distance; + else + target = labels[branchIfFalse.DestinationLabel]; + currentBlock.Add(new IRBranch(PopStack(), + GetBlockFromStartIndex(blocks, currentBlock.EndIndex + 1), + GetBlockFromStartIndex(blocks, target), + branchIfFalse)); + break; + case OpcodeBranchJump branchJump: + int destinationIndex = branchJump.DestinationLabel != string.Empty ? labels[branchJump.DestinationLabel] : index + branchJump.Distance; + currentBlock.Add(new IRJump(GetBlockFromStartIndex(blocks, destinationIndex), branchJump)); + break; + case OpcodeJumpStack _: + throw new NotImplementedException("OpcodeJumpStack is not implemented for optimization because it is non-deterministic. Use OptimizationLevel.None."); + case OpcodeCompareGT _: + case OpcodeCompareLT _: + case OpcodeCompareGTE _: + case OpcodeCompareLTE _: + case OpcodeCompareNE _: + case OpcodeCompareEqual _: + case OpcodeMathAdd _: + case OpcodeMathSubtract _: + case OpcodeMathMultiply _: + case OpcodeMathDivide _: + case OpcodeMathPower _: + temp = CreateTemp(); + IRValue right = PopStack(); + IRValue left = PopStack(); + instruction = new IRBinaryOp(temp, (BinaryOpcode)opcode, left, right); + //currentBlock.Add(instruction); + temp.Parent = instruction; + stack.Push(temp); + break; + case OpcodeMathNegate _: + case OpcodeLogicToBool _: + case OpcodeLogicNot _: + temp = CreateTemp(); + instruction = new IRUnaryOp(temp, opcode, PopStack()); + temp.Parent = instruction; + //currentBlock.Add(instruction); + stack.Push(temp); + break; + case OpcodeCall call: + temp = CreateTemp(); + Stack arguments = new Stack(); + bool hasArgmarker = stack.Count > 0; // Not even an argument marker on the stack - the dominator block must have it + while (stack.Count > 0) + { + IRValue stackResult = PopStack(); + if (stackResult is IRConstant constant && constant.Value is Execution.KOSArgMarkerType) + break; + arguments.Push(stackResult); + } + instruction = new IRCall(temp, call, hasArgmarker, arguments); + if (stack.Count > 0 && !((IRCall)instruction).Direct) + { + ((IRCall)instruction).IndirectMethod = PopStack(); + } + temp.Parent = instruction; + stack.Push(temp); + break; + case OpcodeReturn opcodeReturn: + currentBlock.Add(new IRReturn(opcodeReturn.Depth, opcodeReturn) { Value = PopStack() }); + break; + case OpcodePush opcodePush: + object argument = opcodePush.Argument; + if (argument is string identifier && identifier.StartsWith("$")) + stack.Push(currentBlock.PushVariable(identifier, opcodePush)); + else + stack.Push(new IRConstant(argument, opcodePush)); + break; + case OpcodePushDelegateRelocateLater delegateRelocateLater: + stack.Push(new IRDelegateRelocateLater(delegateRelocateLater.DestinationLabel, delegateRelocateLater.WithClosure, delegateRelocateLater)); + break; + case OpcodePushRelocateLater relocateLater: + stack.Push(new IRRelocateLater(relocateLater.DestinationLabel, relocateLater)); + break; + case OpcodeAddTrigger _: + case OpcodeRemoveTrigger _: + currentBlock.Add(new IRUnaryConsumer(opcode, PopStack(), false)); + break; + case OpcodeWait _: + currentBlock.Add(new IRUnaryConsumer(opcode, PopStack(), true)); + break; + case OpcodePop pop: + currentBlock.Add(new IRPop(PopStack(), pop)); + break; + default: + throw new NotImplementedException($"The Opcode of type {opcode.GetType()} is not implemented."); + } + } + } +} \ No newline at end of file diff --git a/src/kOS.Safe/Compilation/IR/IRCodePart.cs b/src/kOS.Safe/Compilation/IR/IRCodePart.cs new file mode 100644 index 0000000000..515a2a4236 --- /dev/null +++ b/src/kOS.Safe/Compilation/IR/IRCodePart.cs @@ -0,0 +1,132 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using kOS.Safe.Compilation.KS; + +namespace kOS.Safe.Compilation.IR +{ + public class IRCodePart + { + public List MainCode { get; set; } + public List Functions { get; set; } + public List Triggers { get; set; } + public List RootBlocks { get; } = new List(); + public List Blocks { get; } = new List(); + + public IRCodePart(CodePart codePart, List userFunctions, List triggers) + { + if (codePart.InitializationCode.Count > 0) + throw new ArgumentException($"{nameof(codePart)} has initialization code and is structured unexpectedly."); + if (codePart.FunctionsCode.Count > 0) + throw new ArgumentException($"{nameof(codePart)} has function code and is structured unexpectedly."); + + IRBuilder builder = new IRBuilder(); + MainCode = builder.Lower(codePart.MainCode); + Functions = userFunctions.Select(f => new IRFunction(builder, f)).ToList(); + Triggers = triggers.Select(t => new IRTrigger(builder, t)).ToList(); + + Blocks.AddRange(MainCode); + if (MainCode.Count > 0) + RootBlocks.Add(MainCode[0]); + foreach (IRTrigger trigger in Triggers) + { + Blocks.AddRange(trigger.Code); + if (trigger.Code.Count > 0) + RootBlocks.Add(trigger.Code[0]); + } + foreach (IRFunction function in Functions) + { + Blocks.AddRange(function.InitializationCode); + if (function.InitializationCode.Count > 0) + RootBlocks.Add(function.InitializationCode[0]); + foreach (IRFunction.IRFunctionFragment fragment in function.Fragments) + { + Blocks.AddRange(fragment.FunctionCode); + if (fragment.FunctionCode.Count > 0) + RootBlocks.Add(fragment.FunctionCode[0]); + } + } + } + + public void EmitCode(CodePart codePart) + { + IREmitter emitter = new IREmitter(); + foreach (IRTrigger trigger in Triggers) + { + trigger.EmitCode(emitter); + } + foreach (IRFunction function in Functions) + { + function.EmitCode(emitter); + } + codePart.MainCode = emitter.Emit(MainCode); + } + + public class IRTrigger + { + private readonly Trigger trigger; + public string Identifier { get; } + public List Code { get; set; } + public IRTrigger(IRBuilder builder, Trigger trigger) + { + this.trigger = trigger; + Identifier = trigger.Code.FirstOrDefault()?.Label ?? ""; + Code = builder.Lower(trigger.Code); + } + public void EmitCode(IREmitter emitter) + { + trigger.Code.Clear(); + trigger.Code.AddRange(emitter.Emit(Code)); + } + } + + public class IRFunction + { + private readonly UserFunction function; + private readonly List userFunctionFragments; + + public string Identifier => function.Identifier; + public List InitializationCode { get; set; } + private readonly Dictionary fragments = new Dictionary(); + public IReadOnlyCollection Fragments => fragments.Values; + + public IRFunction(IRBuilder builder, UserFunction function) + { + this.function = function; + InitializationCode = builder.Lower(function.InitializationCode); + userFunctionFragments = function.PeekNewCodeFragments().ToList(); + foreach (UserFunctionCodeFragment fragment in userFunctionFragments) + { + fragments.Add(fragment, new IRFunctionFragment(builder, fragment)); + } + userFunctionFragments.Reverse(); + } + + public void EmitCode(IREmitter emitter) + { + function.InitializationCode.Clear(); + function.InitializationCode.AddRange(emitter.Emit(InitializationCode)); + foreach (UserFunctionCodeFragment fragment in userFunctionFragments) + { + fragments[fragment].EmitCode(emitter); + } + } + + public class IRFunctionFragment + { + private readonly UserFunctionCodeFragment fragment; + public List FunctionCode { get; set; } + public IRFunctionFragment(IRBuilder builder, UserFunctionCodeFragment codeFragment) + { + fragment = codeFragment; + FunctionCode = builder.Lower(codeFragment.Code); + } + public void EmitCode(IREmitter emitter) + { + fragment.Code.Clear(); + fragment.Code.AddRange(emitter.Emit(FunctionCode)); + } + } + } + } +} diff --git a/src/kOS.Safe/Compilation/IR/IREmitter.cs b/src/kOS.Safe/Compilation/IR/IREmitter.cs new file mode 100644 index 0000000000..3056f99e35 --- /dev/null +++ b/src/kOS.Safe/Compilation/IR/IREmitter.cs @@ -0,0 +1,57 @@ +using System.Collections.Generic; +using System.Linq; + +namespace kOS.Safe.Compilation.IR +{ + public class IREmitter + { + int labelIndex = 0; + public List Emit(List blocks) + { + List result = new List(); + Dictionary jumpLabels = new Dictionary(); + // Emit Opcodes for each block + foreach (BasicBlock block in blocks) + LabelAndEmit(block, jumpLabels, result); + // Remove single-line jumps from fallthrough blocks + for (int i = 0; i < result.Count; i++) + { + Opcode opcode = result[i]; + if (string.IsNullOrEmpty(opcode.Label) || opcode.Label.StartsWith("@")) + opcode.Label = CreateLabel(i + labelIndex); + if (i >= result.Count - 2) + continue; + if (opcode is OpcodeBranchJump jump && + jump.DestinationLabel != null && jump.DestinationLabel == result[i + 1].Label) + { + foreach (var key in jumpLabels.Keys.ToArray()) + if (jumpLabels[key] >= i) + jumpLabels[key] = jumpLabels[key] - 1; + result.RemoveAt(i); + i--; + } + } + // Restore original labels + foreach (Opcode opcode in result) + { + if (opcode.DestinationLabel != null && + opcode.DestinationLabel.StartsWith("@") && + jumpLabels.ContainsKey(opcode.DestinationLabel)) + { + opcode.DestinationLabel = CreateLabel(jumpLabels[opcode.DestinationLabel]); + } + } + labelIndex += result.Count; + return result; + } + + private void LabelAndEmit(BasicBlock block, Dictionary jumpLabels, List result) + { + jumpLabels.Add(block.Label, result.Count + labelIndex); + result.AddRange(block.EmitOpCodes()); + } + + private static string CreateLabel(int index) + => string.Format("@{0:0000}", index + 1); + } +} diff --git a/src/kOS.Safe/Compilation/IR/IRInstruction.cs b/src/kOS.Safe/Compilation/IR/IRInstruction.cs new file mode 100644 index 0000000000..b0477d47eb --- /dev/null +++ b/src/kOS.Safe/Compilation/IR/IRInstruction.cs @@ -0,0 +1,816 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace kOS.Safe.Compilation.IR +{ + public abstract class IRInstruction + { + public short SourceLine { get; private set; } // line number in the source code that this was compiled from. + public short SourceColumn { get; private set; } // column number of the token nearest the cause of this Opcode. + + // Should-be-static + public abstract bool SideEffects { get; } + internal abstract IEnumerable EmitOpcode(); + protected IRInstruction(Opcode originalOpcode) + { + SourceLine = originalOpcode.SourceLine; + SourceColumn = originalOpcode.SourceColumn; + } + protected Opcode SetSourceLocation(Opcode opcode) + { + if (opcode == null) + return opcode; + opcode.SourceLine = SourceLine; + opcode.SourceColumn = SourceColumn; + return opcode; + } + public void OverwriteSourceLocation(short sourceLine, short sourceColumn) + { + SourceLine = sourceLine; + SourceColumn = sourceColumn; + } + } + public abstract class IRInteractsInstruction : IRInstruction + { + public override bool SideEffects { get; } + protected IRInteractsInstruction(IRValue interactor, Opcode originalOpcode) : base(originalOpcode) + { + if (interactor is IRConstant) + { + SideEffects = false; + return; + } + switch (interactor.Type) + { + case IRValue.ValueType.Value: + SideEffects = false; + break; + default: + SideEffects = true; + break; + } + } + } + public class IRAssign : IRInstruction, ISingleOperandInstruction + { + public enum StoreScope + { + Ambivalent, + Local, + Global + } + public override bool SideEffects => false; + public IRVariableBase Target { get; set; } + public IRValue Value { get; set; } + public StoreScope Scope { get; set; } = StoreScope.Ambivalent; + public bool AssertExists { get; set; } = false; + IRValue ISingleOperandInstruction.Operand { get => Value; set => Value = value; } + + public IRAssign(OpcodeIdentifierBase opcode, IRVariableBase target, IRValue value) : base(opcode) + { + Target = target; + Value = value; + } + internal override IEnumerable EmitOpcode() + { + if (Value != null) + foreach (Opcode opcode in Value.EmitPush()) + yield return opcode; + if (AssertExists) + { + yield return SetSourceLocation(new OpcodeStoreExist(Target.Name)); + yield break; + } + switch (Scope) + { + case StoreScope.Local: + yield return SetSourceLocation(new OpcodeStoreLocal(Target.Name)); + yield break; + case StoreScope.Global: + yield return SetSourceLocation(new OpcodeStoreGlobal(Target.Name)); + yield break; + default: + case StoreScope.Ambivalent: + yield return SetSourceLocation(new OpcodeStore(Target.Name)); + yield break; + } + } + public override string ToString() + => string.Format("{{store {0} -> {1}}}", Value.ToString(), Target.ToString()); + public override bool Equals(object obj) + => obj is IRAssign assignment && + Target.Equals(assignment.Target) && + Value.Equals(assignment.Value); + public override int GetHashCode() + => Target.GetHashCode(); + } + public class IRBinaryOp : IRInstruction, IResultingInstruction, IMultipleOperandInstruction + { + public override bool SideEffects => false; + public IRValue Result { get; set; } + public BinaryOpcode Operation { get; set; } + public IRValue Left { get; set; } + public IRValue Right { get; set; } + public IEnumerable Operands { get { yield return Left; yield return Right; } } + public int OperandCount => 2; + private static Type[] commutativeTypes = + { + typeof(OpcodeCompareEqual), + typeof(OpcodeCompareNE), + typeof(OpcodeCompareGT), + typeof(OpcodeCompareLT), + typeof(OpcodeCompareGTE), + typeof(OpcodeCompareLTE), + typeof(OpcodeMathAdd), + typeof(OpcodeMathMultiply) + }; + IRValue IMultipleOperandInstruction.this[int index] + { + get => index == 0 ? Left : index == 1 ? Right : throw new System.ArgumentOutOfRangeException(); + set + { + if (index == 0) + Left = value; + else if (index == 1) + Right = value; + else + throw new System.ArgumentOutOfRangeException(); + } + } + public bool IsCommutative => commutativeTypes.Contains(Operation.GetType()); + + public IRBinaryOp(IRTemp result, BinaryOpcode operation, IRValue left, IRValue right) : base(operation) + { + Result = result; + Operation = operation; + Left = left; + Right = right; + } + public void SwapOperands() + { + if (!IsCommutative) + return; + //throw new System.InvalidOperationException($"{this} is not commutative."); + (Right, Left) = (Left, Right); + switch (Operation) + { + case OpcodeCompareGT _: + Operation = new OpcodeCompareLTE(); + break; + case OpcodeCompareLT _: + Operation = new OpcodeCompareGTE(); + break; + case OpcodeCompareGTE _: + Operation = new OpcodeCompareLT(); + break; + case OpcodeCompareLTE _: + Operation = new OpcodeCompareGT(); + break; + } + } + internal override IEnumerable EmitOpcode() + { + foreach (Opcode opcode in Left.EmitPush()) + yield return opcode; + foreach (Opcode opcode in Right.EmitPush()) + yield return opcode; + Operation.Label = string.Empty; + yield return SetSourceLocation(Operation); + } + public override string ToString() + => Operation.ToString(); + public override bool Equals(object obj) + { + if (obj is IRBinaryOp binaryOp && + Operation.GetType() == binaryOp.Operation.GetType()) + { + return (Left.Equals(binaryOp.Left) && Right.Equals(binaryOp.Right)) || + (IsCommutative && Left.Equals(binaryOp.Right) && Right.Equals(binaryOp.Left)); + } + return false; + } + public override int GetHashCode() + => Operation.GetHashCode(); + } + public class IRUnaryOp : IRInstruction, IResultingInstruction, ISingleOperandInstruction + { + public override bool SideEffects => false; + public IRValue Result { get; set; } + public Opcode Operation { get; } + public IRValue Operand { get; set; } + public IRUnaryOp(IRTemp result, Opcode operation, IRValue operand) : base(operation) + { + Result = result; + Operation = operation; + Operand = operand; + } + internal override IEnumerable EmitOpcode() + { + foreach (Opcode opcode in Operand.EmitPush()) + yield return opcode; + Operation.Label = string.Empty; + yield return Operation; + } + public override string ToString() + => Operation.ToString(); + public override bool Equals(object obj) + => obj is IRUnaryOp unaryOp && + Operation.GetType() == unaryOp.Operation.GetType() && + Operand.Equals(unaryOp.Operand); + public override int GetHashCode() + => Operation.GetHashCode(); + } + public class IRNoStackInstruction : IRInstruction + { + public override bool SideEffects => false; + public Opcode Operation { get; } + public IRNoStackInstruction(Opcode opcode) : base(opcode) + => Operation = opcode; + internal override IEnumerable EmitOpcode() + { + Operation.Label = string.Empty; + yield return Operation; + } + public override string ToString() + => Operation.ToString(); + public override bool Equals(object obj) + => obj is IRNoStackInstruction instruction && Operation.GetType() == instruction.Operation.GetType(); + public override int GetHashCode() + => Operation.GetHashCode(); + } + public class IRUnaryConsumer : IRInstruction, ISingleOperandInstruction + { + public override bool SideEffects { get; } + public Opcode Operation { get; } + public IRValue Operand { get; set; } + public IRUnaryConsumer(Opcode opcode, IRValue operand, bool sideEffects = false) : base(opcode) + { + Operation = opcode; + Operand = operand; + SideEffects = sideEffects; + } + internal override IEnumerable EmitOpcode() + { + foreach (Opcode opcode in Operand.EmitPush()) + yield return opcode; + Operation.Label = string.Empty; + yield return Operation; + } + public override string ToString() + => Operation.ToString(); + public override bool Equals(object obj) + => obj is IRUnaryConsumer unaryConsumer && + Operation.GetType() == unaryConsumer.Operation.GetType() && + Operand.Equals(unaryConsumer.Operand); + public override int GetHashCode() + => Operation.GetHashCode(); + } + public class IRPop : IRInstruction, ISingleOperandInstruction + { + public override bool SideEffects => false; + public IRValue Value { get; set; } + IRValue ISingleOperandInstruction.Operand { get => Value; set => Value = value; } + public IRPop(IRValue value, OpcodePop opcode) : base(opcode) + => Value = value; + + internal override IEnumerable EmitOpcode() + { + if (Value is IRConstant || (Value is IRVariable && !(Value is IRTemp))) + yield break; + foreach (Opcode opcode in Value.EmitPush()) + yield return opcode; + yield return SetSourceLocation(new OpcodePop()); + } + public override string ToString() + => $"{{pop {Value}}}"; + public override bool Equals(object obj) + => obj is IRPop pop && Value.Equals(pop.Value); + public override int GetHashCode() + => Value.GetHashCode(); + } + public class IRNonVarPush : IRInstruction, IResultingInstruction + { + public override bool SideEffects => false; + public Opcode Operation { get; } + + public IRValue Result { get; } + + public IRNonVarPush(IRValue result, Opcode opcode) : base(opcode) + { + Operation = opcode; + Result = result; + } + internal override IEnumerable EmitOpcode() + { + Operation.Label = string.Empty; + yield return Operation; + } + public override string ToString() + => Operation.ToString(); + public override bool Equals(object obj) + => obj is IRNonVarPush instruction && + Operation.GetType() == instruction.Operation.GetType(); + public override int GetHashCode() + => Operation.GetHashCode(); + } + public class IRSuffixGet : IRInteractsInstruction, IResultingInstruction, ISingleOperandInstruction + { + public IRValue Result { get; set; } + public IRValue Object { get; set; } + public string Suffix { get; set; } + IRValue ISingleOperandInstruction.Operand { get => Object; set => Object = value; } + public IRSuffixGet(IRTemp result, IRValue obj, OpcodeGetMember opcodeGetMember) : base(obj, opcodeGetMember) + { + Result = result; + Object = obj; + Suffix = opcodeGetMember.Identifier; + } + internal override IEnumerable EmitOpcode() + { + foreach (Opcode opcode in Object.EmitPush()) + yield return opcode; + yield return SetSourceLocation(new OpcodeGetMember(Suffix)); + } + public override string ToString() + => string.Format("{{gmb \"{0}\"}}", Suffix); + public override bool Equals(object obj) + => obj is IRSuffixGet suffixGet && + !(suffixGet is IRSuffixGetMethod) && + string.Equals(Suffix, suffixGet.Suffix, System.StringComparison.OrdinalIgnoreCase) && + Object == suffixGet.Object; + public override int GetHashCode() + => (Object, Suffix).GetHashCode(); + } + public class IRSuffixGetMethod : IRSuffixGet + { + public override bool SideEffects => false; + public IRSuffixGetMethod(IRTemp result, IRValue obj, OpcodeGetMethod opcode) : base(result, obj, opcode) { } + internal override IEnumerable EmitOpcode() + { + foreach (Opcode opcode in Object.EmitPush()) + yield return opcode; + yield return SetSourceLocation(new OpcodeGetMethod(Suffix)); + } + public override string ToString() + => string.Format("{{gmet \"{0}\"}}", Suffix); + public override bool Equals(object obj) + => obj is IRSuffixGetMethod suffixGet && + string.Equals(Suffix, suffixGet.Suffix, System.StringComparison.OrdinalIgnoreCase) && + Object == suffixGet.Object; + public override int GetHashCode() + => (Object, Suffix).GetHashCode(); + } + public class IRSuffixSet : IRInteractsInstruction, IMultipleOperandInstruction + { + public override bool SideEffects { get; } + public IRValue Object { get; set; } + public IRValue Value { get; set; } + public IEnumerable Operands { get { yield return Object; yield return Value; } } + public int OperandCount => 2; + IRValue IMultipleOperandInstruction.this[int index] + { + get => index == 0 ? Object : index == 1 ? Value : throw new System.ArgumentOutOfRangeException(); + set + { + if (index == 0) + Object = value; + else if (index == 1) + Value = value; + else + throw new System.ArgumentOutOfRangeException(); + } + } + public string Suffix { get; } + public IRSuffixSet(IRValue obj, IRValue value, OpcodeSetMember opcodeSetMember) : base(obj, opcodeSetMember) + { + Object = obj; + Value = value; + Suffix = opcodeSetMember.Identifier; + } + internal override IEnumerable EmitOpcode() + { + foreach (Opcode opcode in Object.EmitPush()) + yield return opcode; + foreach (Opcode opcode in Value.EmitPush()) + yield return opcode; + yield return SetSourceLocation(new OpcodeSetMember(Suffix)); + } + public override string ToString() + => string.Format("{{smb \"{0}\"}}", Suffix); + public override bool Equals(object obj) + => obj is IRSuffixSet suffixSet && + string.Equals(Suffix, suffixSet.Suffix, System.StringComparison.OrdinalIgnoreCase) && + Object.Equals(suffixSet.Object) && + Value.Equals(suffixSet.Value); + public override int GetHashCode() + => (Object, Suffix).GetHashCode(); + } + public class IRIndexGet : IRInstruction, IResultingInstruction, IMultipleOperandInstruction + { + public override bool SideEffects => false; + public IRValue Result { get; } + public IRValue Object { get; set; } + public IRValue Index { get; set; } + public IEnumerable Operands { get { yield return Object; yield return Index; } } + public int OperandCount => 2; + IRValue IMultipleOperandInstruction.this[int index] + { + get => index == 0 ? Object : index == 1 ? Index : throw new System.ArgumentOutOfRangeException(); + set + { + if (index == 0) + Object = value; + else if (index == 1) + Index = value; + else + throw new System.ArgumentOutOfRangeException(); + } + } + public IRIndexGet(IRTemp result, IRValue obj, IRValue index, OpcodeGetIndex opcode) : base(opcode) + { + Result = result; + Object = obj; + Index = index; + } + internal override IEnumerable EmitOpcode() + { + foreach (Opcode opcode in Object.EmitPush()) + yield return opcode; + foreach (Opcode opcode in Index.EmitPush()) + yield return opcode; + yield return SetSourceLocation(new OpcodeGetIndex()); + } + public override string ToString() + => "{gidx}"; + public override bool Equals(object obj) + => obj is IRIndexGet indexGet && + Object.Equals(indexGet.Object) && + Index.Equals(indexGet.Index); + public override int GetHashCode() + => Object.GetHashCode(); + } + public class IRIndexSet : IRInstruction, IMultipleOperandInstruction + { + public override bool SideEffects => false; + public IRValue Object { get; set; } + public IRValue Index { get; set; } + public IRValue Value { get; set; } + public IEnumerable Operands { get { yield return Object; yield return Index; yield return Value; } } + public int OperandCount => 3; + IRValue IMultipleOperandInstruction.this[int index] + { + get => index == 0 ? Object : index == 1 ? Index : index == 2 ? Value : throw new System.ArgumentOutOfRangeException(); + set + { + if (index == 0) + Object = value; + else if (index == 1) + Index = value; + else if (index == 2) + Value = value; + else + throw new System.ArgumentOutOfRangeException(); + } + } + public IRIndexSet(IRValue obj, IRValue index, IRValue value, OpcodeSetIndex opcode) : base(opcode) + { + Object = obj; + Index = index; + Value = value; + } + internal override IEnumerable EmitOpcode() + { + foreach (Opcode opcode in Object.EmitPush()) + yield return opcode; + foreach (Opcode opcode in Index.EmitPush()) + yield return opcode; + foreach (Opcode opcode in Value.EmitPush()) + yield return opcode; + yield return SetSourceLocation(new OpcodeSetIndex()); + } + public override string ToString() + => "{sidx}"; + public override bool Equals(object obj) + => obj is IRIndexSet indexGet && + Object.Equals(indexGet.Object) && + Index.Equals(indexGet.Index) && + Value.Equals(indexGet.Value); + public override int GetHashCode() + => Object.GetHashCode(); + } + public class IRJump : IRInstruction + { + public override bool SideEffects => false; + public BasicBlock Target { get; set; } + public IRJump(BasicBlock target, OpcodeBranchJump opcode) : base(opcode) + { + Target = target; + } + public IRJump(BasicBlock target, short sourceLine, short sourceColumn) + : this(target, new OpcodeBranchJump() { SourceLine = sourceLine, SourceColumn = sourceColumn }) { } + internal override IEnumerable EmitOpcode() + { + yield return SetSourceLocation(new OpcodeBranchJump() { DestinationLabel = Target.Label }); + } + public override string ToString() + => string.Format("{{jump {0}}}", Target.Label); + public override bool Equals(object obj) + => obj is IRJump jump && + Target == jump.Target; + public override int GetHashCode() + => Target.GetHashCode(); + } + public class IRJumpStack : IRInstruction, ISingleOperandInstruction + { + public override bool SideEffects => false; + public IRValue Distance { get; set; } + IRValue ISingleOperandInstruction.Operand { get => Distance; set => Distance = value; } + public List Targets { get; } = new List(); + public IRJumpStack(IRValue distance, IEnumerable targets, OpcodeJumpStack jumpStack) : base(jumpStack) + { + Distance = distance; + Targets.AddRange(targets); + } + internal override IEnumerable EmitOpcode() + { + foreach (Opcode opcode in Distance.EmitPush()) + yield return opcode; + yield return SetSourceLocation(new OpcodeJumpStack()); + } + public override bool Equals(object obj) + => obj is IRJumpStack jumpStack && + Distance == jumpStack.Distance && + Targets.SequenceEqual(jumpStack.Targets); + public override int GetHashCode() + => Targets.GetHashCode(); + } + public class IRBranch : IRInstruction, ISingleOperandInstruction + { + public override bool SideEffects => false; + public IRValue Condition { get; set; } + IRValue ISingleOperandInstruction.Operand { get => Condition; set => Condition = value; } + public BasicBlock True { get; set; } + public BasicBlock False { get; set; } + public bool PreferFalse { get; set; } = false; + public IRBranch(IRValue condition, BasicBlock onTrue, BasicBlock onFalse, BranchOpcode opcodeBranch) : base(opcodeBranch) + { + Condition = condition; + True = onTrue; + False = onFalse; + PreferFalse = opcodeBranch is OpcodeBranchIfFalse; + } + internal override IEnumerable EmitOpcode() + { + foreach (Opcode opcode in Condition.EmitPush()) + yield return opcode; + if (PreferFalse) + { + yield return SetSourceLocation(new OpcodeBranchIfFalse() { DestinationLabel = False.Label }); + yield return SetSourceLocation(new OpcodeBranchJump() { DestinationLabel = True.Label }); + } + else + { + yield return SetSourceLocation(new OpcodeBranchIfTrue() { DestinationLabel = True.Label }); + yield return SetSourceLocation(new OpcodeBranchJump() { DestinationLabel = False.Label }); + } + } + public override string ToString() + => string.Format("{{br.? {0}/{1}}}", True.Label, False.Label); + public override bool Equals(object obj) + => obj is IRBranch branch && + Condition.Equals(branch.Condition) && + True == branch.True && + False == branch.False; + public override int GetHashCode() + => True.GetHashCode() ^ False.GetHashCode(); + } + public class IRCall : IRInstruction, IResultingInstruction, IMultipleOperandInstruction + { + protected static readonly Function.FunctionManager functionManager = new Function.FunctionManager(null); + public override bool SideEffects { get; } + public IRValue Result { get; set; } + public string Function { get; } + public List Arguments { get; } = new List(); + public IEnumerable Operands => Enumerable.Reverse(Arguments); + public int OperandCount => Arguments.Count; + IRValue IMultipleOperandInstruction.this[int index] + { + get => Arguments[index]; + set => Arguments[index] = value; + } + public IRValue IndirectMethod { get; internal set; } + public bool Direct { get; } + public bool EmitArgMarker { get; set; } + private IRCall(IRTemp target, OpcodeCall opcode, bool emitArgMarker) : base(opcode) + { + Result = target; + Function = (string)opcode.Destination; + Direct = opcode.Direct; + SideEffects = CheckIfFunctionHasSideEffects(opcode); + EmitArgMarker = emitArgMarker; + } + private bool CheckIfFunctionHasSideEffects(OpcodeCall opcode) + { + if (!Direct) + return true; + if (opcode.Destination is string functionName && functionManager.Exists(functionName)) + { + functionName = functionName.ToLower().Replace("()", ""); + switch (functionName) + { + case "vcrs": + case "vectorcrossproduct": + case "vdot": + case "vectordotproduct": + case "vxcl": + case "vectorexclude": + case "vang": + case "vectorangle": + case "clearscreen": + case "hudtext": + case "add": + case "remove": + case "processor": + case "edit": + case "printlist": + case "node": + case "v": + case "r": + case "q": + case "createorbit": + case "rotatefromto": + case "lookdirup": + case "angleaxis": + case "latlng": + case "vessel": + case "body": + case "bodyexists": + case "bodyatmosphere": + case "bounds": + case "heading": + case "slidenote": + case "note": + case "getvoice": + case "stopallvoices": + case "time": + case "timestamp": + case "timespan": + case "hsv": + case "hsva": + case "vecdraw": + case "vecdrawargs": + case "clearvecdraws": + case "clearguis": + case "gui": + case "career": + case "lex": + case "lexicon": + case "list": + case "pidloop": + case "queue": + case "stack": + case "uniqueset": + case "abs": + case "mod": + case "floor": + case "ceiling": + case "round": + case "sqrt": + case "ln": + case "log10": + case "min": + case "max": + case "random": + case "randomseed": + case "char": + case "unchar": + case "range": + case "constant": + case "sin": + case "cos": + case "tan": + case "arcsin": + case "arccos": + case "arctan": + case "arctan2": + case "anglediff": + return false; + case "addAlarm": + case "listAlarms": + case "deleteAlarm": + case "buildlist": + case "positionat": + case "velocityat": + case "orbitat": + case "allwaypoints": + case "waypoint": + case "print": + case "printat": + case "logfile": + case "debugdump": + case "debugfreezegame": + case "profileresult": + case "makebuiltindelegate": + case "droppriority": + case "stage": + case "warpto": + case "transfer": + case "transferall": + case "run": + case "load": + case "toggleflybywire": + case "selectautopilotmode": + case "reboot": + case "shutdown": + case "scriptpath": + case "switch": + case "cd": + case "chdir": + case "copy_deprecated": + case "rename_file_deprecated": + case "rename_volume_deprecated": + case "delete_deprecated": + case "copypath": + case "movepath": + case "deletepath": + case "writejson": + case "readjson": + case "exists": + case "open": + case "create": + case "createdir": + case "path": + case "volume": + default: + return true; + } + } + else + return true; + } + internal override IEnumerable EmitOpcode() + { + if (EmitArgMarker) + { + if (IndirectMethod != null) + foreach (Opcode opcode in IndirectMethod.EmitPush()) + yield return opcode; + yield return new OpcodePush(new Execution.KOSArgMarkerType()); + } + foreach (IRValue argument in Arguments) + { + foreach (Opcode opcode in argument.EmitPush()) + yield return opcode; + } + yield return SetSourceLocation(new OpcodeCall(Function)); + } + + public IRCall(IRTemp target, OpcodeCall opcode, bool emitArgMarker, IRValue argument) : this(target, opcode, emitArgMarker) + { + Arguments.Add(argument); + } + public IRCall(IRTemp target, OpcodeCall opcode, bool emitArgMarker, IEnumerable arguments) : this(target, opcode, emitArgMarker) + { + Arguments.AddRange(arguments); + } + public IRCall(IRTemp target, OpcodeCall opcode, bool emitArgMarker, params IRValue[] arguments) : this(target, opcode, emitArgMarker) + { + Arguments.AddRange(arguments); + } + public override string ToString() + => string.Format("{{call {0}({1})}}", Function.Trim('(', ')'), string.Join(",", Arguments.Select(a => a.ToString()))); + public override bool Equals(object obj) + => obj is IRCall call && + string.Equals(Function.Replace("()", ""), call.Function.Replace("()", ""), System.StringComparison.OrdinalIgnoreCase) && + Arguments.SequenceEqual(call.Arguments); + public override int GetHashCode() + => Function.ToLower().GetHashCode(); + } + public class IRReturn : IRInstruction, ISingleOperandInstruction + { + public override bool SideEffects => false; + public IRValue Value { get; set; } + IRValue ISingleOperandInstruction.Operand { get => Value; set => Value = value; } + public short Depth { get; internal set; } + public IRReturn(short depth, OpcodeReturn opcode) : base(opcode) + => Depth = depth; + internal override IEnumerable EmitOpcode() + { + if (Value != null) + foreach (Opcode opcode in Value.EmitPush()) + yield return opcode; + else + yield return SetSourceLocation(new OpcodePush(null)); + yield return SetSourceLocation(new OpcodeReturn(Depth)); + } + public override string ToString() + => string.Format("{return {0} deep}", Depth); + public override bool Equals(object obj) + => obj is IRReturn ret && + Depth == ret.Depth && + Value.Equals(ret.Value); + public override int GetHashCode() + => base.GetHashCode(); + } +} diff --git a/src/kOS.Safe/Compilation/IR/IRScope.cs b/src/kOS.Safe/Compilation/IR/IRScope.cs new file mode 100644 index 0000000000..4437d0c486 --- /dev/null +++ b/src/kOS.Safe/Compilation/IR/IRScope.cs @@ -0,0 +1,142 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace kOS.Safe.Compilation.IR +{ + public class IRScope + { + private readonly Dictionary variables = + new Dictionary(StringComparer.OrdinalIgnoreCase); + private IRScope parent; + private readonly HashSet childScopes = new HashSet(); + private readonly HashSet blocks = new HashSet(); + + private int nextChildIndex = 0; + private int index; + + public IRScope ParentScope + { + get => parent; + set + { + parent?.childScopes.Remove(this); + parent = value; + if (parent != null) + { + parent.childScopes.Add(this); + index = parent.nextChildIndex++; + } + else + index = 0; + } + } + public IReadOnlyCollection Children => childScopes; + public IReadOnlyCollection Blocks => blocks; + public BasicBlock HeaderBlock { get; set; } + public BasicBlock FooterBlock { get; set; } + public IReadOnlyCollection Variables => variables.Values; + public IReadOnlyCollection VariableNames => variables.Keys; + public bool IsGlobalScope { get; internal set; } = false; + + public IRScope(IRScope parent) + { + ParentScope = parent; + } + + public void StoreLocalVariable(IRVariableBase variable) + { + variables[variable.Name] = variable; + } + public void StoreGlobalVariable(IRVariableBase variable) + { + if (!IsGlobalScope) + ParentScope.StoreGlobalVariable(variable); + else + StoreLocalVariable(variable); + } + public void StoreVariable(IRVariableBase variable) + { + if (!TryStoreVariable(variable)) + StoreGlobalVariable(variable); + } + public bool TryStoreVariable(IRVariableBase variable) + { + if (variables.ContainsKey(variable.Name)) + { + variables[variable.Name] = variable; + return true; + } + return ParentScope?.TryStoreVariable(variable) ?? false; + } + + public IRVariableBase GetVariableNamed(string name) + { + if (variables.ContainsKey(name)) + return variables[name]; + return ParentScope?.GetVariableNamed(name); + } + + public bool IsVariableInScope(string name) + { + if (variables.ContainsKey(name)) + return true; + return ParentScope?.IsVariableInScope(name) ?? false; + } + public bool IsVariableInScope(IRVariableBase variable) + { + return variable.Scope.IsEncompassedBy(this); + } + + public IRScope GetScopeForVariableNamed(string name) + { + if (IsGlobalScope) + return this; + if (variables.ContainsKey(name)) + return this; + return ParentScope.GetScopeForVariableNamed(name); + } + + public void ClearVariable(string name) + { + variables.Remove(name); + } + public void ClearVariable(IRVariableBase variable) + => ClearVariable(variable.Name); + + public bool IsEncompassedBy(IRScope scope) + { + if (scope.IsGlobalScope) + return true; + if (scope.childScopes.Contains(this)) + return true; + return ParentScope?.IsEncompassedBy(scope) ?? false; + } + + public IRScope GetGlobalScope() + { + if (IsGlobalScope) + return this; + return ParentScope.GetGlobalScope(); + } + + public void EnrollBlock(BasicBlock block) + { + block.Scope?.RemoveBlock(block); + blocks.Add(block); + } + public void RemoveBlock(BasicBlock block) + => blocks.Remove(block); + + public override string ToString() + => $"IRScope: {IndexString()}"; + public string IndexString() + { + if (IsGlobalScope || ParentScope == null) + return "Global"; + if (ParentScope.IsGlobalScope) + return $"{index}"; + return $"{ParentScope.IndexString()}.{index}"; + } + } +} diff --git a/src/kOS.Safe/Compilation/IR/IRValue.cs b/src/kOS.Safe/Compilation/IR/IRValue.cs new file mode 100644 index 0000000000..5fe141e9ba --- /dev/null +++ b/src/kOS.Safe/Compilation/IR/IRValue.cs @@ -0,0 +1,191 @@ +using System.Collections.Generic; + +namespace kOS.Safe.Compilation.IR +{ + public abstract class IRValue + { + public enum ValueType + { + Unknown = 0, + Value = 1, + GameObject = 2 + } + public ValueType Type { get; } + internal abstract IEnumerable EmitPush(); + } + + public class IRConstant : IRValue + { + public object Value { get; } + protected readonly short sourceLine, sourceColumn; + public IRConstant(object value, Opcode opcode) : this(value, opcode.SourceLine, opcode.SourceColumn) { } + public IRConstant(object value, IRInstruction instruction) : this(value, instruction.SourceLine, instruction.SourceColumn) { } + public IRConstant(object value, short sourceLine, short sourceColumn) + { + Value = value; + this.sourceLine = sourceLine; + this.sourceColumn = sourceColumn; + } + internal override IEnumerable EmitPush() + { + yield return new OpcodePush(Value) + { + SourceLine = sourceLine, + SourceColumn = sourceColumn + }; + } + public override bool Equals(object obj) + => obj is IRConstant constant && Value.Equals(constant.Value) || Value.Equals(obj); + public override int GetHashCode() + => Value.GetHashCode(); + public override string ToString() + => Value.ToString(); + } + public abstract class IRVariableBase : IRValue + { + public string Name { get; } + public virtual IRScope Scope { get; protected set; } + public IRVariableBase(string name, IRScope declaringScope) + { + Name = name; + Scope = declaringScope; + } + public override string ToString() + => $"{Name} {Scope.IndexString()}"; + public override bool Equals(object obj) + => obj is IRVariableBase variable && + (Scope == variable.Scope || + Scope.IsEncompassedBy(variable.Scope) || + variable.Scope.IsEncompassedBy(Scope)) && + string.Equals(Name, variable.Name, System.StringComparison.OrdinalIgnoreCase); + public override int GetHashCode() + => Name.ToLower().GetHashCode(); + } + public class IRVariable : IRVariableBase + { + protected readonly short sourceLine, sourceColumn; + public bool IsLock { get; } + public override IRScope Scope { get => base.Scope; } + public IRVariable(string name, IRScope scope, IRInstruction instruction, bool isLock = false) : + this(name, scope, instruction.SourceLine, instruction.SourceColumn, isLock) { } + public IRVariable(OpcodeIdentifierBase opcode, IRScope scope, bool isLock = false) : + this(opcode.Identifier, scope, opcode, isLock) { } + public IRVariable(string name, IRScope scope, Opcode opcode, bool isLock = false) : + this(name, scope, opcode.SourceLine, opcode.SourceColumn, isLock) { } + public IRVariable(string name, IRScope scope, short sourceLine, short sourceColumn, bool isLock = false) : + base(name, scope) + { + IsLock = isLock; + this.sourceLine = sourceLine; + this.sourceColumn = sourceColumn; + } + internal override IEnumerable EmitPush() + { + yield return new OpcodePush(Name) + { + SourceLine = sourceLine, + SourceColumn = sourceColumn + }; + } + } + public class IRRelocateLater : IRConstant + { + public IRRelocateLater(string value, OpcodePushRelocateLater opcode) : base(value, opcode) { } + + internal override IEnumerable EmitPush() + { + yield return new OpcodePushRelocateLater((string)Value) + { + SourceLine = sourceLine, + SourceColumn = sourceColumn + }; + } + } + public class IRDelegateRelocateLater : IRRelocateLater + { + public bool WithClosure { get; } + public IRDelegateRelocateLater(string value, bool withClosure, OpcodePushDelegateRelocateLater opcode) : base(value, opcode) + { + WithClosure = withClosure; + } + internal override IEnumerable EmitPush() + { + yield return new OpcodePushDelegateRelocateLater((string)Value, WithClosure) + { + SourceLine = sourceLine, + SourceColumn = sourceColumn + }; + } + } + public class IRTemp : IRVariableBase + { + private bool isPromoted = false; + + public int ID { get; } + public IRInstruction Parent { get; internal set; } + + public IRTemp(int id) : base($"$.temp.{id}", null) + { + ID = id; + Scope = null; + } + public IRAssign PromoteToVariable(IRScope scope) + { + isPromoted = true; + Scope = scope; + return new IRAssign(new OpcodeStoreLocal(Name) + { + SourceLine = -1, + SourceColumn = 0 + }, + this, + this) + { + Scope = IRAssign.StoreScope.Local + }; + } + internal override IEnumerable EmitPush() + { + if (isPromoted) + yield return new OpcodePush(Name) + { + SourceLine = -1, + SourceColumn = 0 + }; + else + foreach (Opcode opcode in Parent.EmitOpcode()) + yield return opcode; + } + public override bool Equals(object obj) + { + if (obj is IRTemp temp) + { + /*if (isPromoted) + { + return temp.isPromoted && + Scope == temp.Scope && + string.Equals(Name, temp.Name, System.StringComparison.OrdinalIgnoreCase); + }*/ + return Parent.Equals(temp.Parent); + } + if (obj is IRVariable variable) + { + return isPromoted && + base.Equals(variable); + } + return false; + } + public override string ToString() + { + if (isPromoted) + return base.ToString(); + return $"| {Parent}"; + } + public override int GetHashCode() + => base.GetHashCode(); + } + public class IRParameter : IRValue + { + internal override IEnumerable EmitPush() => System.Linq.Enumerable.Empty(); + } +} diff --git a/src/kOS.Safe/Compilation/IR/IResultingInstruction.cs b/src/kOS.Safe/Compilation/IR/IResultingInstruction.cs new file mode 100644 index 0000000000..a9c7bfcdbb --- /dev/null +++ b/src/kOS.Safe/Compilation/IR/IResultingInstruction.cs @@ -0,0 +1,7 @@ +namespace kOS.Safe.Compilation.IR +{ + public interface IResultingInstruction + { + IRValue Result { get; } + } +} \ No newline at end of file diff --git a/src/kOS.Safe/Compilation/KS/Compiler.cs b/src/kOS.Safe/Compilation/KS/Compiler.cs index 2de644b33f..564fd51c13 100644 --- a/src/kOS.Safe/Compilation/KS/Compiler.cs +++ b/src/kOS.Safe/Compilation/KS/Compiler.cs @@ -202,10 +202,22 @@ public CodePart Compile(int startLineNum, ParseTree tree, Context context, Compi { PreProcess(tree); CompileProgram(tree); + if (options.OptimizationLevel != OptimizationLevel.None) + { + Optimize(part, context, options); + } } return part; } + public static void Optimize(CodePart code, Context context, CompilerOptions options) + { + IR.IRCodePart irCodePart = new IR.IRCodePart(code, context.UserFunctions.PeekNewFunctions(), context.Triggers.PeekNewParts()); + Optimization.Optimizer optimizer = new Optimization.Optimizer(options.OptimizationLevel); + optimizer.Optimize(irCodePart); + irCodePart.EmitCode(code); + } + private void CompileProgram(ParseTree tree) { currentCodeSection = part.MainCode; diff --git a/src/kOS.Safe/Compilation/KS/TriggerCollection.cs b/src/kOS.Safe/Compilation/KS/TriggerCollection.cs index fb9de153bb..82181e3965 100644 --- a/src/kOS.Safe/Compilation/KS/TriggerCollection.cs +++ b/src/kOS.Safe/Compilation/KS/TriggerCollection.cs @@ -35,6 +35,9 @@ public List GetParts() return GetParts(triggers.Values.ToList()); } + + internal List PeekNewParts() + => newTriggers; public IEnumerable GetNewParts() { List parts = GetParts(newTriggers); diff --git a/src/kOS.Safe/Compilation/KS/UserFunction.cs b/src/kOS.Safe/Compilation/KS/UserFunction.cs index dd397cc5f8..8e2a96e808 100644 --- a/src/kOS.Safe/Compilation/KS/UserFunction.cs +++ b/src/kOS.Safe/Compilation/KS/UserFunction.cs @@ -135,6 +135,9 @@ public string GetUserFunctionLabel(int expressionHash) return String.Format("{0}-{1}", Identifier, expressionHash.ToString("x")); } + internal IEnumerable PeekNewCodeFragments() + => newFunctions; + public CodePart GetCodePart() { var mergedPart = new CodePart diff --git a/src/kOS.Safe/Compilation/KS/UserFunctionCollection.cs b/src/kOS.Safe/Compilation/KS/UserFunctionCollection.cs index 23d2446d2d..87faf7cc2e 100644 --- a/src/kOS.Safe/Compilation/KS/UserFunctionCollection.cs +++ b/src/kOS.Safe/Compilation/KS/UserFunctionCollection.cs @@ -113,6 +113,8 @@ public List GetParts() return GetParts(userFuncs.Values.ToList()); } + internal List PeekNewFunctions() + => newUserFuncs; public IEnumerable GetNewParts() { // new locks or functions diff --git a/src/kOS.Safe/Compilation/Opcode.cs b/src/kOS.Safe/Compilation/Opcode.cs index d7a65a8d17..6637211e6e 100644 --- a/src/kOS.Safe/Compilation/Opcode.cs +++ b/src/kOS.Safe/Compilation/Opcode.cs @@ -479,12 +479,17 @@ public override void Execute(ICpu cpu) object right = cpu.PopValueArgument(); object left = cpu.PopValueArgument(); + object result = ExecuteCalculation(left, right); + cpu.PushArgumentStack(result); + } + + public object ExecuteCalculation(object left, object right) + { var operands = new OperandPair(left, right); Calculator calc = Calculator.GetCalculator(operands); Operands = operands; - object result = ExecuteCalculation(calc); - cpu.PushArgumentStack(result); + return ExecuteCalculation(calc); } protected virtual object ExecuteCalculation(Calculator calc) @@ -1313,27 +1318,29 @@ public override void Execute(ICpu cpu) { Structure value = cpu.PopStructureEncapsulatedArgument(); + cpu.PushArgumentStack(StaticOperation(value)); + } + + public static object StaticOperation(object value) + { var scalarValue = value as ScalarValue; if (scalarValue != null && scalarValue.IsValid) { - cpu.PushArgumentStack(-scalarValue); - return; + return -scalarValue; } - + // Generic last-ditch to catch any sort of object that has // overloaded the unary negate operator '-'. // (For example, kOS.Suffixed.Vector and kOS.Suffixed.Direction) Type t = value.GetType(); - MethodInfo negateMe = t.GetMethod("op_UnaryNegation", BindingFlags.FlattenHierarchy |BindingFlags.Static | BindingFlags.Public); + MethodInfo negateMe = t.GetMethod("op_UnaryNegation", BindingFlags.FlattenHierarchy | BindingFlags.Static | BindingFlags.Public); if (negateMe != null) { - object result = negateMe.Invoke(null, new[]{value}); - cpu.PushArgumentStack(result); + return negateMe.Invoke(null, new[] { value }); } else throw new KOSUnaryOperandTypeException("negate", value); - } } @@ -1462,8 +1469,12 @@ public override void Execute(ICpu cpu) // is to also change integers and floats into booleans. Thus the call to // Convert.ToBoolean(): object value = cpu.PopValueArgument(); + cpu.PushArgumentStack(StaticOperation(value)); + } + public static object StaticOperation(object value) + { bool result = Convert.ToBoolean(value); - cpu.PushArgumentStack(Structure.FromPrimitive(result)); + return Structure.FromPrimitive(result); } } @@ -1485,21 +1496,22 @@ public class OpcodeLogicNot : Opcode public override void Execute(ICpu cpu) { object value = cpu.PopValueArgument(); - object result; - + cpu.PushArgumentStack(StaticOperation(value)); + } + public static object StaticOperation(object value) + { // Convert to bool instead of cast in case the identifier is stored // as an encapsulated BooleanValue, preventing an unboxing collision. // Wrapped in a try/catch since the Convert framework doesn't have a // way to determine if a type can be converted. try { - result = !Convert.ToBoolean(value); + return Structure.FromPrimitive(!Convert.ToBoolean(value)); } catch { throw new KOSCastException(value.GetType(), typeof(BooleanValue)); } - cpu.PushArgumentStack(Structure.FromPrimitive(result)); } } @@ -2004,7 +2016,7 @@ public override string ToString() public class OpcodePush : Opcode { [MLField(1,false)] - private object Argument { get; set; } + public object Argument { get; private set; } protected override string Name { get { return "push"; } } public override ByteCode Code { get { return ByteCode.PUSH; } } diff --git a/src/kOS.Safe/Compilation/Optimization/ExtendedBasicBlock.cs b/src/kOS.Safe/Compilation/Optimization/ExtendedBasicBlock.cs new file mode 100644 index 0000000000..1456d26a86 --- /dev/null +++ b/src/kOS.Safe/Compilation/Optimization/ExtendedBasicBlock.cs @@ -0,0 +1,73 @@ +using System.Collections.Generic; +using System.Linq; +using kOS.Safe.Compilation.IR; + +namespace kOS.Safe.Compilation.Optimization +{ + public class ExtendedBasicBlock + { + public List Blocks { get; } = new List(); + public IEnumerable Sucessors => sucessors; + public IEnumerable Predecessors => predecessors; + private readonly HashSet predecessors = new HashSet(); + private readonly HashSet sucessors = new HashSet(); + private ExtendedBasicBlock(BasicBlock startingBlock) + { + AddBlock(startingBlock); + Blocks.Sort(Comparer.Create((x, y) => x.ID.CompareTo(y.ID))); + } + private void AddBlock(BasicBlock block) + { + Blocks.Add(block); + block.ExtendedBlock = this; + foreach (BasicBlock successor in block.Successors) + { + if (!successor.Predecessors.Skip(1).Any()) + AddBlock(successor); + else + { + ExtendedBasicBlock extendedSuccessor = successor.ExtendedBlock ?? new ExtendedBasicBlock(successor); + AddSuccessor(extendedSuccessor); + } + } + } + + public static ExtendedBasicBlock CreateExtendedBlockTree(BasicBlock root) + { + ExtendedBasicBlock resultBlock = new ExtendedBasicBlock(root); + return resultBlock; + } + public static IEnumerable DumpTree(ExtendedBasicBlock root) + { + HashSet visited = new HashSet(); + return DumpTree(root, visited); + } + private static IEnumerable DumpTree(ExtendedBasicBlock root, HashSet visited) + { + yield return root; + foreach (ExtendedBasicBlock successor in root.Sucessors) + { + if (visited.Add(successor)) + { + foreach (ExtendedBasicBlock further in DumpTree(successor, visited)) + yield return further; + } + } + } + + public void AddSuccessor(ExtendedBasicBlock successor) + { + sucessors.Add(successor); + successor.AddPredecessor(this); + } + protected void AddPredecessor(ExtendedBasicBlock predecessor) + { + predecessors.Add(predecessor); + } + + public override string ToString() + { + return $"ExtendedBasicBlock: {string.Join(", ", Blocks.Select(bb => $"#{bb.ID}"))}"; + } + } +} diff --git a/src/kOS.Safe/Compilation/Optimization/IOptimizationPass.cs b/src/kOS.Safe/Compilation/Optimization/IOptimizationPass.cs new file mode 100644 index 0000000000..c503f8f51c --- /dev/null +++ b/src/kOS.Safe/Compilation/Optimization/IOptimizationPass.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; +using kOS.Safe.Compilation.IR; + +namespace kOS.Safe.Compilation.Optimization +{ + public interface IOptimizationPass + { + OptimizationLevel OptimizationLevel { get; } + short SortIndex { get; } + } + public interface IOptimizationPass : IOptimizationPass + { + void ApplyPass(List code); + } + public interface IHolisticOptimizationPass : IOptimizationPass + { + void ApplyPass(IRCodePart codePart); + } + public interface ILinkedOptimizationPass : IOptimizationPass + { + Optimizer Optimizer { set; } + void ApplyPass(); + } +} diff --git a/src/kOS.Safe/Compilation/Optimization/InterimCPU.cs b/src/kOS.Safe/Compilation/Optimization/InterimCPU.cs new file mode 100644 index 0000000000..b54c6b4a4b --- /dev/null +++ b/src/kOS.Safe/Compilation/Optimization/InterimCPU.cs @@ -0,0 +1,153 @@ +using System; +using System.Collections.Generic; +using kOS.Safe.Encapsulation; +using kOS.Safe.Execution; + +namespace kOS.Safe.Compilation.Optimization +{ + internal class InterimCPU : ICpu + { + public int InstructionPointer { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + public double SessionTime => throw new NotImplementedException(); + public List ProfileResult => throw new NotImplementedException(); + public int NextTriggerInstanceId => throw new NotImplementedException(); + public int InstructionsThisUpdate => throw new NotImplementedException(); + public bool IsPoppingContext => throw new NotImplementedException(); + + private readonly Stack stack = new Stack(); + public void Boot() + { + stack.Clear(); + } + public object PopArgumentStack() + => stack.PopArgument(); + public object PopValueArgument(bool barewordOkay = false) + => GetValue(PopArgumentStack(), barewordOkay); + public void PushArgumentStack(object item) + => stack.PushArgument(item); + public object PopValueEncapsulatedArgument(bool barewordOkay = false) + => Structure.FromPrimitive(PopValueArgument(barewordOkay)); + public object GetValue(object testValue, bool barewordOkay = false) + { + // $cos cos named variable + // cos() cos trigonometric function + // cos string literal "cos" + + // If it's a variable, meaning it starts with "$" but + // does NOT have a value like $<.....>, which are special + // flags used internally: + var identifier = testValue as string; + if (identifier == null || + identifier.Length <= 1 || + identifier[0] != '$' || + identifier[1] == '<') + { + return testValue; + } + + throw new InvalidOperationException("Only compile-time constants can be accessed. Contact a kOS developer if you see this message."); + } + + public void AddPopContextNotifyee(IPopContextNotifyee notifyee) + => throw new NotImplementedException(); + public TriggerInfo AddTrigger(int triggerFunctionPointer, InterruptPriority priority, int instanceId, bool immediate, List closure) + => throw new NotImplementedException(); + public TriggerInfo AddTrigger(TriggerInfo trigger, bool immediate) + => throw new NotImplementedException(); + public TriggerInfo AddTrigger(UserDelegate del, InterruptPriority priority, int instanceId, bool immediate, List args) + => throw new NotImplementedException(); + public TriggerInfo AddTrigger(UserDelegate del, InterruptPriority priority, int instanceId, bool immediate, params Structure[] args) + => throw new NotImplementedException(); + public void AddVariable(Variable variable, string identifier, bool local, bool overwrite = false) + => throw new NotImplementedException(); + public void AssertValidDelegateCall(IUserDelegate userDelegate) + => throw new NotImplementedException(); + public void BreakExecution(bool manual) + => throw new NotImplementedException(); + public bool BuiltInExists(string functionName) + => throw new NotImplementedException(); + public void CallBuiltinFunction(string functionName) + => throw new NotImplementedException(); + public void CancelCalledTriggers(int triggerFunctionPointer, int instanceId) + => throw new NotImplementedException(); + public void CancelCalledTriggers(TriggerInfo trigger) + => throw new NotImplementedException(); + public void Dispose() + => throw new NotImplementedException(); + public void DropBackPriority() + => throw new NotImplementedException(); + public string DumpStack() + => throw new NotImplementedException(); + public string DumpVariables() + => throw new NotImplementedException(); + public int GetArgumentStackSize() + => throw new NotImplementedException(); + public List GetCallTrace() + => throw new NotImplementedException(); + public List GetCodeFragment(int contextLines) + => throw new NotImplementedException(); + public List GetCurrentClosure() + => throw new NotImplementedException(); + public IProgramContext GetCurrentContext() + => throw new NotImplementedException(); + public Opcode GetCurrentOpcode() + => throw new NotImplementedException(); + public SubroutineContext GetCurrentSubroutineContext() + => throw new NotImplementedException(); + public Opcode GetOpcodeAt(int instructionPtr) + => throw new NotImplementedException(); + public Structure GetStructureEncapsulatedArgument(Structure testValue, bool barewordOkay = false) + => throw new NotImplementedException(); + public bool IdentifierExistsInScope(string identifier) + => throw new NotImplementedException(); + public void KOSFixedUpdate(double deltaTime) + => throw new NotImplementedException(); + public IUserDelegate MakeUserDelegate(int entryPoint, bool withClosure) + => throw new NotImplementedException(); + public object PeekRawArgument(int digDepth, out bool checkOkay) + => throw new NotImplementedException(); + public object PeekRawScope(int digDepth, out bool checkOkay) + => throw new NotImplementedException(); + public Structure PeekStructureEncapsulatedArgument(int digDepth, bool barewordOkay = false) + => throw new NotImplementedException(); + public object PeekValueArgument(int digDepth, bool barewordOkay = false) + => throw new NotImplementedException(); + public object PeekValueEncapsulatedArgument(int digDepth, bool barewordOkay = false) + => throw new NotImplementedException(); + public object PopScopeStack(int howMany) + => throw new NotImplementedException(); + public Structure PopStructureEncapsulatedArgument(bool barewordOkay = false) + => throw new NotImplementedException(); + + public void PushNewScope(short scopeId, short parentScopeId) + => throw new NotImplementedException(); + public void PushScopeStack(object thing) + => throw new NotImplementedException(); + public void RemovePopContextNotifyee(IPopContextNotifyee notifyee) + => throw new NotImplementedException(); + public void RemoveTrigger(int triggerFunctionPointer, int instanceId) + => throw new NotImplementedException(); + public void RemoveTrigger(TriggerInfo trigger) + => throw new NotImplementedException(); + public void RemoveVariable(string identifier) + => throw new NotImplementedException(); + public void RunProgram(List program) + => throw new NotImplementedException(); + public void SetGlobal(string identifier, object value) + => throw new NotImplementedException(); + public void SetNewLocal(string identifier, object value) + => throw new NotImplementedException(); + public void SetValue(string identifier, object value) + => throw new NotImplementedException(); + public void SetValueExists(string identifier, object value) + => throw new NotImplementedException(); + public void StartCompileStopwatch() + => throw new NotImplementedException(); + public void StopCompileStopwatch() + => throw new NotImplementedException(); + public IProgramContext SwitchToProgramContext() + => throw new NotImplementedException(); + public void YieldProgram(YieldFinishedDetector yieldTracker) + => throw new NotImplementedException(); + } +} diff --git a/src/kOS.Safe/Compilation/Optimization/OptimizationLevel.cs b/src/kOS.Safe/Compilation/Optimization/OptimizationLevel.cs new file mode 100644 index 0000000000..474db720bf --- /dev/null +++ b/src/kOS.Safe/Compilation/Optimization/OptimizationLevel.cs @@ -0,0 +1,49 @@ +#pragma warning disable IDE0130 // Namespace does not match folder structure +namespace kOS.Safe.Compilation +#pragma warning restore IDE0130 // Namespace does not match folder structure +{ + /* + * O1: + * 10. Suffix replacement: + * Replace CONSTANT: values with the constant + * Replace ship fields with their alias + * 20. Constant propagation + * 30. Constant folding + * 50. Dead code elimination + * 1050. Peephole optimizations: + * Replace lex indexing with string constant with suffixing where possible + * Replace parameterless suffix method calls with get member (this feels like cheating...) + * Replace calls to VectorDotProduct with multiplication + * Replace --X with X + * Replace !!X with X + * Branch logical simplification (e.g. !X branch = X branch!) + * Algebraic simplification (e.g. A*B+A*C = A*(B+C), A+-B=A-B, A--B=A+B, -A+B=B-A, X*X*...*X=N^X) + * 32000. Remove unnecessary scope pushes/pops + * + * O2: + * 1000. Common expression elimination + * Particularly: Any expression of 3 opcodes used more than twice, + * or any expression of >3 opcodes used more than once + * 2100. Code motion - moving expressions outside loops if they do not depend on loop variables + * 2150. Carry's - moving the N-1 D lookup of >1D lists outside the innermost loop + * Loop jamming? (Combining adjacent loops into one) + * Unswitching (moving conditional evaluation outside the loop) - low priority + * Linear function test replacement - low priority + * + * O3: + * 3100. Local function inlining + * 3200. Constant propagation to local functions + * 3500. Loop stack manipulation (delayed setting of either index or aggregator) + * + * O4: + * 4000. Constant loop unrolling + */ + public enum OptimizationLevel : int + { + None = 0, // No changes whatsoever + Minimal = 1, // Only changes that trim opcodes without changing any flow + Balanced = 2, // Only changes that trim opcodes without increasing file size + Aggressive = 3, // Favor trimming opcodes over file size. + Extreme = 4 // Include constant loop unrolling + } +} diff --git a/src/kOS.Safe/Compilation/Optimization/OptimizationTools.cs b/src/kOS.Safe/Compilation/Optimization/OptimizationTools.cs new file mode 100644 index 0000000000..0c241d449c --- /dev/null +++ b/src/kOS.Safe/Compilation/Optimization/OptimizationTools.cs @@ -0,0 +1,138 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using kOS.Safe.Compilation.IR; + +namespace kOS.Safe.Compilation.Optimization +{ + public static class OptimizationTools + { + /*public static IEnumerable FindExpressionsMatching(this IEnumerable instructions, Predicate predicate) + => instructions.SelectMany(i => ExpressionMatchDepthFirst(i, predicate)); + + public static IEnumerable FindFirstExpressionsMatching(this IEnumerable instructions, Predicate predicate) + => instructions.SelectMany(i => ExpressionMatchBreadthFirst(i, predicate)); + */ + + public static IEnumerable DepthFirst(this IEnumerable instructions) + => instructions.SelectMany(DepthFirst); + + public static IEnumerable FindInstructionsMatching(this IEnumerable instructions, Predicate predicate) + => instructions.SelectMany(i => InstructionMatchDepthFirst(i, predicate)); + + public static IEnumerable FindFirstInstructionsMatching(this IEnumerable instructions, Predicate predicate) + => instructions.SelectMany(i => InstructionMatchBreadthFirst(i, predicate)); + + private static IEnumerable ExpressionMatchDepthFirst(IRInstruction instruction, Predicate predicate) + { + if (instruction is ISingleOperandInstruction singleOperandInstruction) + { + if (singleOperandInstruction.Operand is IRTemp temp) + { + foreach (IRTemp predecessorMatch in ExpressionMatchDepthFirst(temp.Parent, predicate)) + yield return predecessorMatch; + if (predicate(temp.Parent)) + yield return temp; + } + } + else if (instruction is IMultipleOperandInstruction multipleOperandInstruction) + { + foreach (IRValue operand in multipleOperandInstruction.Operands) + { + if (operand is IRTemp temp) + { + foreach (IRTemp predecessorMatch in ExpressionMatchDepthFirst(temp.Parent, predicate)) + yield return predecessorMatch; + if (predicate(temp.Parent)) + yield return temp; + } + } + } + } + + private static IEnumerable ExpressionMatchBreadthFirst(IRInstruction instruction, Predicate predicate) + { + if (instruction is ISingleOperandInstruction singleOperandInstruction) + { + if (singleOperandInstruction.Operand is IRTemp temp) + { + if (predicate(temp.Parent)) + yield return temp; + foreach (IRTemp predecessorMatch in ExpressionMatchBreadthFirst(temp.Parent, predicate)) + yield return predecessorMatch; + } + } + else if (instruction is IMultipleOperandInstruction multipleOperandInstruction) + { + foreach (IRValue operand in multipleOperandInstruction.Operands) + { + if (operand is IRTemp temp && predicate(temp.Parent)) + yield return temp; + } + foreach (IRValue operand in multipleOperandInstruction.Operands) + { + if (operand is IRTemp temp) + { + foreach (IRTemp predecessorMatch in ExpressionMatchBreadthFirst(temp.Parent, predicate)) + yield return predecessorMatch; + } + } + } + } + + private static IEnumerable InstructionMatchDepthFirst(IRInstruction instruction, Predicate predicate) + { + foreach (IRInstruction match in CrawlTreeForMatch(instruction, predicate, InstructionMatchDepthFirst)) + yield return match; + if (predicate(instruction)) + yield return instruction; + } + + private static IEnumerable InstructionMatchBreadthFirst(IRInstruction instruction, Predicate predicate) + { + if (predicate(instruction)) + yield return instruction; + foreach (IRInstruction match in CrawlTreeForMatch(instruction, predicate, InstructionMatchBreadthFirst)) + yield return match; + } + + public static IEnumerable DepthFirst(this IRInstruction instruction) + { + if (instruction is ISingleOperandInstruction singleOperandInstruction) + { + if (singleOperandInstruction.Operand is IRTemp temp) + foreach (IRInstruction predecessor in DepthFirst(temp.Parent)) + yield return predecessor; + } + else if (instruction is IMultipleOperandInstruction multipleOperandInstruction) + { + foreach (IRValue operand in multipleOperandInstruction.Operands) + { + if (operand is IRTemp temp) + foreach (IRInstruction predecessor in DepthFirst(temp.Parent)) + yield return predecessor; + } + } + yield return instruction; + } + + private static IEnumerable CrawlTreeForMatch(IRInstruction instruction, Predicate predicate, Func, IEnumerable> searchFunc) + { + if (instruction is ISingleOperandInstruction singleOperandInstruction) + { + if (singleOperandInstruction.Operand is IRTemp temp) + foreach (IRInstruction match in searchFunc(temp.Parent, predicate)) + yield return match; + } + else if (instruction is IMultipleOperandInstruction multipleOperandInstruction) + { + foreach (IRValue operand in multipleOperandInstruction.Operands) + { + if (operand is IRTemp temp) + foreach (IRInstruction match in searchFunc(temp.Parent, predicate)) + yield return match; + } + } + } + } +} diff --git a/src/kOS.Safe/Compilation/Optimization/Optimizer.cs b/src/kOS.Safe/Compilation/Optimization/Optimizer.cs new file mode 100644 index 0000000000..6345b5e5bf --- /dev/null +++ b/src/kOS.Safe/Compilation/Optimization/Optimizer.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using kOS.Safe.Compilation.IR; +using kOS.Safe.Function; +using kOS.Safe.Utilities; + +namespace kOS.Safe.Compilation.Optimization +{ + [AssemblyWalk(InterfaceType = typeof(IOptimizationPass), StaticRegisterMethod = "RegisterMethod")] + public class Optimizer + { + public OptimizationLevel OptimizationLevel { get; } + public List Blocks { get; private set; } + public List ExtendedBlocks { get; private set; } + public HashSet RootBlocks { get; private set; } + public IRCodePart Code { get; private set; } + internal static InterimCPU InterimCPU { get; } = new InterimCPU(); + public static IFunctionManager FunctionManager => shared.FunctionManager; + + private static readonly SafeSharedObjects shared = new SafeSharedObjects() { Cpu = InterimCPU }; + private readonly SortedSet optimizationPasses = new SortedSet( + Comparer.Create((a, b) => a.SortIndex.CompareTo(b.SortIndex))); + private readonly static HashSet availablePassTypes = new HashSet(); + + static Optimizer() + { + shared.FunctionManager = new FunctionManager(shared); + } + + public Optimizer(OptimizationLevel optimizationLevel) + { + OptimizationLevel = optimizationLevel; + foreach (Type type in availablePassTypes) + { + IOptimizationPass pass = (IOptimizationPass)Activator.CreateInstance(type); + optimizationPasses.Add(pass); + if (pass is ILinkedOptimizationPass linkedPass) + linkedPass.Optimizer = this; + } + } + public static void RegisterMethod(Type type) + { + availablePassTypes.Add(type); + } + + public List Optimize(IRCodePart codePart) + { + Code = codePart; + Blocks = codePart.Blocks; + RootBlocks = new HashSet(codePart.RootBlocks); + + List rootExtendedBlocks = new List( + codePart.RootBlocks.Select(b => ExtendedBasicBlock.CreateExtendedBlockTree(b))); + ExtendedBlocks = new List( + rootExtendedBlocks.SelectMany(ExtendedBasicBlock.DumpTree)); + + foreach (IOptimizationPass pass in optimizationPasses) + { + if (pass.OptimizationLevel > OptimizationLevel) + continue; + + SafeHouse.Logger.Log($"Applying optimization pass: {pass.GetType()}."); + switch (pass) + { + case ILinkedOptimizationPass linkedPass: + linkedPass.ApplyPass(); + break; + case IHolisticOptimizationPass codePartpass: + codePartpass.ApplyPass(Code); + break; + case IOptimizationPass blockPass: + blockPass.ApplyPass(Blocks); + break; + case IOptimizationPass extendedBlockPass: + extendedBlockPass.ApplyPass(ExtendedBlocks); + break; + case IOptimizationPass instructionPass: + foreach (BasicBlock block in Blocks) + instructionPass.ApplyPass(block.Instructions); + break; + default: + SafeHouse.Logger.LogWarning($"{pass.GetType()}, implementing IOptimizingPass, uses an unsupported generic parameter."); + break; + } + } + return Blocks; + } + } +} diff --git a/src/kOS.Safe/Compilation/Optimization/Passes/ConstantFolding.cs b/src/kOS.Safe/Compilation/Optimization/Passes/ConstantFolding.cs new file mode 100644 index 0000000000..ab3bb56e0b --- /dev/null +++ b/src/kOS.Safe/Compilation/Optimization/Passes/ConstantFolding.cs @@ -0,0 +1,339 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using kOS.Safe.Compilation.IR; +using kOS.Safe.Exceptions; + +namespace kOS.Safe.Compilation.Optimization.Passes +{ + public class ConstantFolding : IOptimizationPass + { + public OptimizationLevel OptimizationLevel => OptimizationLevel.Minimal; + public short SortIndex => 30; + + public void ApplyPass(List blocks) + { + Queue worklist = new Queue(blocks); + IEnumerator enumerator = blocks.GetEnumerator(); + while (enumerator.MoveNext()) + { + ApplyPass(enumerator.Current); + } + } + private static void ApplyPass(BasicBlock block) + { + for (int i = 0; i < block.Instructions.Count; i++) + { + IRInstruction instruction = block.Instructions[i]; + switch (instruction) + { + case IRPop pop: + if (AttemptReductionToConstant(pop.Value) is IRConstant) + { + block.Instructions.RemoveAt(i); + i--; + } + continue; + case IRAssign assign: + assign.Value = AttemptReductionToConstant(assign.Value); + break; + case IRSuffixSet suffixSet: + suffixSet.Value = AttemptReductionToConstant(suffixSet.Value); + suffixSet.Object = AttemptReductionToConstant(suffixSet.Object); + break; + case IRIndexSet indexSet: + indexSet.Value = AttemptReductionToConstant(indexSet.Value); + indexSet.Object = AttemptReductionToConstant(indexSet.Object); + indexSet.Index = AttemptReductionToConstant(indexSet.Index); + break; + case IRBranch branch: + if (branch.True == branch.False) + block.Instructions[i] = new IRJump(branch.True, branch.SourceLine, branch.SourceColumn); + else if (AttemptReductionToConstant(branch.Condition) is IRConstant branchConstant) + { + BasicBlock permanentBlock, deprecatedBlock; + (permanentBlock, deprecatedBlock) = Convert.ToBoolean(branchConstant.Value) ? (branch.True, branch.False) : (branch.False, branch.True); + block.Instructions[i] = new IRJump(permanentBlock, new OpcodeBranchJump() { SourceLine = branch.SourceLine, SourceColumn = branch.SourceColumn }); + block.RemoveSuccessor(deprecatedBlock); + // TODO: Resolve any phi values in the deprecated block and consider re-running folding on that block. + } + break; + } + } + } + private static IRValue AttemptReductionToConstant(IRValue input) + { + if (input is IRConstant constant) + return constant; + if (!(input is IRTemp temp)) + return input; + return AttemptReduction(temp.Parent); + } + public static IRValue AttemptReduction(IRInstruction instruction) + { + switch (instruction) + { + case IRUnaryOp unaryOp: + return ReduceUnary(unaryOp); + case IRBinaryOp binaryOp: + return ReduceBinary(binaryOp); + case IRSuffixGet suffixGet: + return ReduceSuffixGet(suffixGet); + case IRIndexGet indexGet: + return ReduceIndexGet(indexGet); + case IRCall call: + return ReduceCall(call); + case IResultingInstruction resulting: + return resulting.Result; + } + throw new ArgumentException($"{instruction.GetType()} is not supported for constant folding."); + } + private static IRValue ReduceUnary(IRUnaryOp instruction) + { + if (instruction.Operand is IRTemp temp) + instruction.Operand = AttemptReduction(temp.Parent); + + if (instruction.Operand is IRConstant constant) + { + object input = constant.Value; + IRValue result; + try + { + switch (instruction.Operation) + { + case OpcodeMathNegate _: + result = new IRConstant(OpcodeMathNegate.StaticOperation(input), instruction); + break; + case OpcodeLogicNot _: + result = new IRConstant(OpcodeLogicNot.StaticOperation(input), instruction); + break; + case OpcodeLogicToBool _: + result = new IRConstant(OpcodeLogicToBool.StaticOperation(input), instruction); + break; + default: + result = instruction.Result; + break; + } + instruction.Result = result; + } + catch (KOSUnaryOperandTypeException unaryTypeException) + { + throw new KOSCompileException(instruction, unaryTypeException); + } + } + return instruction.Result; + } + private static IRValue ReduceBinary(IRBinaryOp instruction) + { + if (instruction.Left is IRTemp tempL) + { + instruction.Left = AttemptReduction(tempL.Parent); + } + if (instruction.Right is IRTemp tempR) + { + instruction.Right = AttemptReduction(tempR.Parent); + } + // Put constants to the right, if there are any + if (instruction.IsCommutative && instruction.Left is IRConstant && !(instruction.Right is IRConstant)) + { + instruction.SwapOperands(); + } + // If this is false, neither are constants after the last step, unless this isn't commutative, in which case this cleverness doesn't matter. + if (instruction.Right is IRConstant constantR) + { + // If this is true, both are constants + if (instruction.Left is IRConstant constantL) + { + object left = constantL.Value; + object right = constantR.Value; + try + { + IRConstant result = new IRConstant(instruction.Operation.ExecuteCalculation(left, right), instruction); + instruction.Result = result; + } + catch (KOSBinaryOperandTypeException binaryTypeException) + { + throw new KOSCompileException(instruction, binaryTypeException); + } + return instruction.Result; + } + else if (instruction.IsCommutative) + { + // The right is constant and the left is not... + // But what if left.Parent is commutative with this operation and has a constant? + if (instruction.Left is IRTemp temp && + temp.Parent is IRBinaryOp leftOp && + leftOp.Operation.GetType() == instruction.Operation.GetType() && + leftOp.Right is IRConstant constantL1) + { + object right = constantR.Value; + object left = constantL1.Value; + try + { + IRConstant result = new IRConstant(instruction.Operation.ExecuteCalculation(left, right), instruction); + instruction.Result = result; + leftOp.Right = result; + } + catch (KOSBinaryOperandTypeException binaryTypeException) + { + throw new KOSCompileException(instruction, binaryTypeException); + } + return leftOp.Result; + } + } + // Shortcuts for math operations where both sides don't need to be constant + switch (instruction.Operation) + { + case OpcodeMathMultiply _: + // X * 0 = 0 + if (Encapsulation.ScalarIntValue.Zero.Equals(constantR.Value)) + return constantR; + if (ReduceDivMult(instruction, constantR, out IRValue newResult)) + return newResult; + break; + case OpcodeMathDivide _: + if (Encapsulation.ScalarIntValue.Zero.Equals(constantR.Value)) + throw new KOSCompileException(instruction, new DivideByZeroException()); + if (ReduceDivMult(instruction, constantR, out newResult)) + return newResult; + break; + case OpcodeMathAdd _: + case OpcodeMathSubtract _: + // X +- 0 = X + if (Encapsulation.ScalarIntValue.Zero.Equals(constantR.Value)) + return instruction.Left; + break; + case OpcodeMathPower _: + // X^0 = 1 + if (Encapsulation.ScalarIntValue.Zero.Equals(constantR.Value)) + return new IRConstant(Encapsulation.ScalarIntValue.One, instruction); + // X^1 = X + if (Encapsulation.ScalarIntValue.One.Equals(constantR.Value)) + return instruction.Left; + break; + } + } + else + { + switch (instruction.Operation) + { + case OpcodeMathDivide _: + // 0 / X = 0 + // Technically not true when X = 0 + // But that would otherwise throw a "Tried to push infinite on to the stack" error + // So this is an acceptable assumption that improves performance and eliminates an error. + // TODO: Add an "EXIT" (EOP) command to the language because this will break the + // PRINT(1/0) shortcut to cause a program to terminate. + if (instruction.Left is IRConstant constantL && + Encapsulation.ScalarIntValue.Zero.Equals(constantL.Value)) + return constantL; + // X / X = 1 + // Technically not true when X = 0 + // But that would otherwise throw a "Tried to push infinite on to the stack" error + // So this is an acceptable assumption that improves performance and eliminates an error. + if (instruction.Left == instruction.Right) + return new IRConstant(Encapsulation.ScalarIntValue.One, instruction); + break; + } + } + return instruction.Result; + } + private static bool ReduceDivMult(IRBinaryOp instruction, IRConstant secondOperand, out IRValue newResult) + { + // X */ 1 = X + if (Encapsulation.ScalarIntValue.One.Equals(secondOperand.Value)) + { + newResult = instruction.Left; + return true; + } + // X */ -1 = -X + if (secondOperand.Value.Equals(-Encapsulation.ScalarIntValue.One)) + { + IRTemp tempResult = instruction.Result as IRTemp; + tempResult.Parent = new IRUnaryOp( + tempResult, + new OpcodeMathNegate() + { + SourceColumn = instruction.SourceColumn, + SourceLine = instruction.SourceLine + }, + instruction.Left); + newResult = tempResult; + return true; + } + newResult = null; + return false; + } + + private static IRValue ReduceSuffixGet(IRSuffixGet instruction) + { + if (instruction.Object is IRTemp temp) + { + instruction.Object = AttemptReduction(temp.Parent); + } + return instruction.Result; + } + private static IRValue ReduceIndexGet(IRIndexGet instruction) + { + if (instruction.Object is IRTemp tempObj) + { + instruction.Object = AttemptReduction(tempObj.Parent); + } + if (instruction.Index is IRTemp tempIndex) + { + instruction.Index = AttemptReduction(tempIndex.Parent); + } + return instruction.Result; + } + private static IRValue ReduceCall(IRCall instruction) + { + // TODO: Add reduction for specific functions. E.g. mod(X, 1) = 0, round/ceiling/floor, Ln/Log10, min/max + for (int i = instruction.Arguments.Count - 1; i >= 0; i--) + { + if (instruction.Arguments[i] is IRTemp temp) + instruction.Arguments[i] = AttemptReduction(temp.Parent); + } + string functionName = instruction.Function.Replace("()", ""); + if (Optimizer.FunctionManager.Exists(functionName) && instruction.Arguments.All(arg => arg is IRConstant)) + { + try + { + switch (functionName) + { + case "abs": + case "mod": + case "floor": + case "ceiling": + case "round": + case "sqrt": + case "ln": + case "log10": + case "min": + case "max": + case "sin": + case "cos": + case "tan": + case "arcsin": + case "arccos": + case "arctan": + case "arctan2": + case "anglediff": + InterimCPU interimCPU = Optimizer.InterimCPU; + interimCPU.Boot(); // Clear the stack out of caution. + interimCPU.PushArgumentStack(new Execution.KOSArgMarkerType()); + foreach (IRValue arg in instruction.Arguments) + interimCPU.PushArgumentStack(((IRConstant)arg).Value); + Optimizer.FunctionManager.CallFunction(functionName); + instruction.Result = new IRConstant(interimCPU.PopValueArgument(), instruction); + return instruction.Result; + } + } + catch (KOSException e) + { + throw new KOSCompileException(instruction, e); + } + } + return instruction.Result; + } + } +} diff --git a/src/kOS.Safe/Compilation/Optimization/Passes/ConstantPropagation.cs b/src/kOS.Safe/Compilation/Optimization/Passes/ConstantPropagation.cs new file mode 100644 index 0000000000..7d04469f5c --- /dev/null +++ b/src/kOS.Safe/Compilation/Optimization/Passes/ConstantPropagation.cs @@ -0,0 +1,508 @@ +using kOS.Safe.Compilation.IR; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace kOS.Safe.Compilation.Optimization.Passes +{ + public class ConstantPropagation : IHolisticOptimizationPass + { + public OptimizationLevel OptimizationLevel => OptimizationLevel.Minimal; + + public short SortIndex => 20; + + public void ApplyPass(IRCodePart codePart) + { + Dictionary> funcExternalWrites = new Dictionary>(); + Dictionary> funcExternalReads = new Dictionary>(); + + // For each function, find the sets of non-local variables read and those written to. + foreach (IRCodePart.IRFunction function in codePart.Functions) + { + HashSet externalWrites = new HashSet(); + HashSet externalReads = new HashSet(); + foreach (IRCodePart.IRFunction.IRFunctionFragment fragment in function.Fragments) + { + AnalyzeFunction(fragment.FunctionCode, externalWrites, externalReads); + } + funcExternalWrites[function.Identifier] = externalWrites; + funcExternalReads[function.Identifier] = externalReads; + } + foreach (IRCodePart.IRTrigger trigger in codePart.Triggers) + { + HashSet externalWrites = new HashSet(); + HashSet externalReads = new HashSet(); + AnalyzeFunction(trigger.Code, externalWrites, externalReads); + funcExternalWrites[trigger.Identifier] = externalWrites; + funcExternalReads[trigger.Identifier] = externalReads; + } + + Dictionary> variablesPropagated = new Dictionary>(); + Dictionary> variablesUnpropagated = new Dictionary>(); + + Dictionary inOutData = new Dictionary(); + Dictionary storedFuncs = new Dictionary(); + + foreach (BasicBlock rootBlock in codePart.RootBlocks) + { + Dictionary map = MapIncoming(rootBlock, + funcExternalWrites, + out Dictionary storedFunctions); + foreach (KeyValuePair pair in map) + inOutData.Add(pair.Key, pair.Value); + foreach (KeyValuePair pair in storedFunctions) + storedFuncs.Add(pair.Key, pair.Value); + } + + foreach (BasicBlock block in codePart.Blocks) + { + if (block.Scope.IsGlobalScope) + PropagateWithinBlock(block, inOutData, + storedFuncs, + new HashSet(), + variablesUnpropagated, + funcExternalReads, + funcExternalWrites); + else + { + if (!variablesPropagated.ContainsKey(block.Scope)) + { + variablesPropagated.Add(block.Scope, new HashSet()); + variablesUnpropagated.Add(block.Scope, new HashSet()); + } + PropagateWithinBlock(block, inOutData, + storedFuncs, + variablesPropagated[block.Scope], + variablesUnpropagated, + funcExternalReads, + funcExternalWrites); + } + } + + foreach (IRScope scope in variablesPropagated.Keys) + { + if (variablesUnpropagated.ContainsKey(scope)) + variablesPropagated[scope].ExceptWith(variablesUnpropagated[scope]); + foreach (IRVariableBase variable in variablesPropagated[scope]) + scope.ClearVariable(variable); + } + } + + private static void AnalyzeFunction(List functionCode, HashSet writes, HashSet reads) + { + if (functionCode.Any()) + reads.UnionWith(functionCode.First().Scope.GetGlobalScope().Variables); + foreach (BasicBlock block in functionCode) + { + foreach (IRInstruction instruction in block.Instructions) + { + if (instruction is IRAssign assignment && + assignment.Target.Scope.IsGlobalScope) + writes.Add(assignment.Target); + } + } + } + + private static void RescopeFunctionVars(HashSet variables, IRScope scope) + { + HashSet temp = new HashSet(); + foreach(IRVariableBase variable in variables) + { + if (scope.IsVariableInScope(variable.Name)) + temp.Add(scope.GetVariableNamed(variable.Name)); + else + temp.Add(variable); + } + variables.Clear(); + variables.UnionWith(temp); + } + + private static Dictionary MapIncoming(BasicBlock rootBlock, + Dictionary> funcExternalWrites, + out Dictionary storedFunctions) + { + storedFunctions = new Dictionary(); + + HashSet GetValueOrDefault(Dictionary> dict, BasicBlock key, IEqualityComparer comparer) + { + if (!dict.ContainsKey(key)) + { + if (comparer == null) + comparer = EqualityComparer.Default; + HashSet result = new HashSet(comparer); + dict.Add(key, result); + return result; + } + return dict[key]; + } + + Dictionary> incoming = new Dictionary>(); + Dictionary> blacklistIn = new Dictionary>(); + + Dictionary> outgoing = new Dictionary>(); + Dictionary> blacklistOut = new Dictionary>(); + Dictionary> defs = new Dictionary>(); + + HashSet basicBlocks = new HashSet(); + Queue worklist = new Queue(); + worklist.Enqueue(rootBlock); + + while (worklist.Count > 0) + { + BasicBlock block = worklist.Dequeue(); + basicBlocks.Add(block); + + if (!defs.ContainsKey(block)) + { + ParseDefs(block, + out Dictionary defsMade, + GetValueOrDefault(blacklistOut, block, null), + storedFunctions, + funcExternalWrites); + defs[block] = new HashSet<(IRVariableBase variable, IRAssign value)>(defsMade.Select(kvp => (kvp.Key, kvp.Value)), VariableTupleEqualityComparer.Instance); + } + + incoming.Remove(block); + HashSet<(IRVariableBase variable, IRAssign value)> defsIn = GetValueOrDefault(incoming, block, VariableTupleEqualityComparer.Instance); + blacklistIn.Remove(block); + HashSet localBlacklist = GetValueOrDefault(blacklistIn, block, null); + BasicBlock firstPredecessor = block.Predecessors.FirstOrDefault(); + if (firstPredecessor != null) + { + defsIn.UnionWith(GetValueOrDefault(outgoing, firstPredecessor, VariableTupleEqualityComparer.Instance)); + localBlacklist.UnionWith(GetValueOrDefault(blacklistOut, firstPredecessor, null)); + } + + foreach (BasicBlock predecessor in block.Predecessors.Skip(1)) + { + if (!outgoing.ContainsKey(predecessor)) + continue; + defsIn.IntersectWith(GetValueOrDefault(outgoing, predecessor, VariableTupleEqualityComparer.Instance)); + localBlacklist.UnionWith(GetValueOrDefault(blacklistOut, predecessor, null)); + } + + defsIn.RemoveWhere(vv => !(vv.variable.Scope == block.Scope || block.Scope.IsEncompassedBy(vv.variable.Scope))); + defsIn.RemoveWhere(vv => localBlacklist.Contains(vv.variable)); + + HashSet<(IRVariableBase variable, IRAssign value)> defsOut = new HashSet<(IRVariableBase variable, IRAssign value)>(defsIn, VariableTupleEqualityComparer.Instance); + HashSet<(IRVariableBase variable, IRAssign value)> definitions = defs[block]; + + defsOut.UnionWith(defsIn); + foreach (var def in definitions) + { + defsOut.RemoveWhere(vv => vv.variable.Equals(def.variable)); + defsOut.Add(def); + } + defsOut.RemoveWhere(vv => localBlacklist.Contains(vv.variable)); + + if (outgoing.ContainsKey(block) && defsOut.SetEquals(outgoing[block])) + continue; + + outgoing[block] = defsOut; + blacklistOut[block].UnionWith(localBlacklist); + foreach (BasicBlock successor in block.Successors) + worklist.Enqueue(successor); + } + + Dictionary map = new Dictionary(); + foreach (BasicBlock block in basicBlocks) + map.Add(block, new BlockDictionaries(incoming[block], blacklistIn[block])); + + return map; + } + + private static void ParseDefs(BasicBlock block, + out Dictionary defs, + HashSet blacklist, + Dictionary storedFunctions, + Dictionary> funcExternalWrites) + { + defs = new Dictionary(); + List instructions = block.Instructions; + for (int i = 0; i < block.Instructions.Count; i++) + { + IRInstruction instruction = instructions[i]; + // Step through instructions (depth first) and replace variables + // with their cached constant whenever they're available. + foreach (IRInstruction nestedInstruction in instruction.DepthFirst()) + { + RemoveVarsWrittenInFunction(nestedInstruction, + block.Scope, + defs, + storedFunctions, + funcExternalWrites); + } + + switch (instruction) + { + // On a local assignment by a constant, cache that constant against the variable. + case IRAssign assignment: + if (!blacklist.Contains(assignment.Target) && + !assignment.Target.Scope.IsGlobalScope) + { + defs[assignment.Target] = assignment; + } + + // On encountering a PushRelocateLater, note the scope for its closure variables. + if (assignment.Value is IRRelocateLater lockOrFunctionPointer) + { + string pointer = ((string)lockOrFunctionPointer.Value).Split('-').First(); + storedFunctions[assignment.Target] = pointer; + // Re-scope the stored variables from Global to the current scope. + RescopeFunctionVars(funcExternalWrites[pointer], block.Scope); + } + break; + + // On encountering a trigger: + // Clear the cache of any variables that are written in that trigger or body. + // Blacklist any variables that are written in the trigger or body. + case IRUnaryConsumer trigger: + if (trigger.Operation is OpcodeAddTrigger) + { + string destination = (string)((IRConstant)trigger.Operand).Value; + if (funcExternalWrites.ContainsKey(destination)) + { + foreach (IRVariableBase variable in funcExternalWrites[destination]) + defs.Remove(variable); + + // Re-scope the stored variables from Global to the current scope. + RescopeFunctionVars(funcExternalWrites[destination], block.Scope); + blacklist.UnionWith(funcExternalWrites[destination]); + } + } + break; + } + } + } + + private static void RemoveVarsWrittenInFunction(IRInstruction instr, + IRScope scope, + Dictionary defsCreated, + Dictionary storedFunctions, + Dictionary> funcExternalWrites) + { + if (instr is IRCall call) + { + // On encountering a Call, where that identifier is in our dictionary of function closure variables. + // Inject the assignment call to any constant variables that are read inside the function. + // Clear the cache of any variables that are written in that function. + IRVariableBase functionCall = scope.GetVariableNamed(call.Function); + if (functionCall != null && storedFunctions.ContainsKey(functionCall)) + { + string funcRef = storedFunctions[functionCall]; + + foreach (IRVariableBase variable in funcExternalWrites[funcRef]) + defsCreated.Remove(variable); + } + } + } + + private static void PropagateWithinBlock(BasicBlock block, + Dictionary inOutData, + Dictionary storedFunctions, + HashSet scopeVarsPropagated, + Dictionary> variablesUnpropagated, + Dictionary> externalVariablesRead, + Dictionary> externalVariablesWritten) + { + BlockDictionaries localInOut = inOutData[block]; + + List instructions = block.Instructions; + + inOutData[block].InitializeForPropagation( + out Dictionary constantCache, + out Dictionary assignmentInstructions, + out HashSet blacklist); + + for (int i = 0; i < instructions.Count; i++) + { + IRInstruction instruction = instructions[i]; + // Step through instructions (depth first) and replace variables + // with their cached constant whenever they're available. + foreach (IRInstruction nestedInstruction in instruction.DepthFirst()) + { + AttemptPropagation(nestedInstruction, + instructions, + ref i, + block.Scope, + constantCache, + storedFunctions, + externalVariablesRead, + externalVariablesWritten,assignmentInstructions, + variablesUnpropagated); + } + + switch (instruction) + { + // On a local assignment by a constant, cache that constant against the variable. + case IRAssign assignment: + if (!blacklist.Contains(assignment.Target) && + !assignment.Target.Scope.IsGlobalScope) + { + if (assignment.Value is IRTemp temp) + assignment.Value = ConstantFolding.AttemptReduction(temp.Parent); + if (assignment.Value is IRConstant constantValue) + { + scopeVarsPropagated.Add(assignment.Target); + instructions.RemoveAt(i); + assignmentInstructions[assignment.Target] = assignment; + constantCache[assignment.Target] = constantValue; + i--; + } + else + { + scopeVarsPropagated.Remove(assignment.Target); + assignmentInstructions.Remove(assignment.Target); + constantCache.Remove(assignment.Target); + } + } + + // On encountering a PushDelegateRelocateLater, note which variables are cached. + // Store the intersections of that set and the function variable sets. + else if (assignment.Value is IRDelegateRelocateLater functionPointer) + { + string pointer = ((string)functionPointer.Value).Split('-').First(); + storedFunctions[assignment.Target] = pointer; + // Re-scope the stored variables from Global to the current scope. + // Writes were accomplished on the first pass. + RescopeFunctionVars(externalVariablesRead[pointer], block.Scope); + } + + // On encountering a Lock call, we will:: + // Inject the assignment call to any constant variables that are read inside the function. + // Clear the cache of any variables that are written in that lock. + else if (assignment.Value is IRRelocateLater lockPointer) + { + string pointer = ((string)lockPointer.Value).Split('-').First(); + storedFunctions[assignment.Target] = pointer; + // Re-scope the stored variables from Global to the current scope. + // Writes were accomplished on the first pass. + RescopeFunctionVars(externalVariablesRead[pointer], block.Scope); + } + break; + + // On encountering a trigger: + // Inject the assignment call to any constant variables that are read inside the trigger or body. + // Clear the cache of any variables that are written in that trigger or body. + // Blacklist any variables that are written in the trigger or body. + case IRUnaryConsumer trigger: + if (trigger.Operation is OpcodeAddTrigger) + { + string destination = (string)((IRConstant)trigger.Operand).Value; + if (externalVariablesRead.ContainsKey(destination)) + { + foreach (IRVariableBase variable in externalVariablesRead[destination].Union( + externalVariablesWritten[destination])) + if (assignmentInstructions.ContainsKey(variable)) + { + instructions.Insert(i++, assignmentInstructions[variable]); + assignmentInstructions.Remove(variable); + variablesUnpropagated[variable.Scope].Add(variable); + } + + foreach (IRVariableBase variable in externalVariablesWritten[destination]) + constantCache.Remove(variable); + + blacklist.UnionWith(externalVariablesWritten[destination]); + } + } + break; + } + } + } + + private static void AttemptPropagation(IRInstruction instruction, + List instructions, + ref int i, + IRScope scope, + Dictionary constantCache, + Dictionary storedFunctions, + Dictionary> funcExternalReads, + Dictionary> funcExternalWrites, + Dictionary assignmentInstructions, + Dictionary> variablesUnpropagated) + { + if (instruction is IRCall call) + { + // On encountering a Call, where that identifier is in our dictionary of function closure variables. + // Inject the assignment call to any constant variables that are read inside the function. + // Clear the cache of any variables that are written in that function. + IRVariableBase functionCall = scope.GetVariableNamed(call.Function); + if (functionCall != null && storedFunctions.ContainsKey(functionCall)) + { + string funcRef = storedFunctions[functionCall]; + foreach (IRVariableBase variable in funcExternalReads[funcRef].Union( + funcExternalWrites[funcRef])) + if (assignmentInstructions.ContainsKey(variable)) + { + instructions.Insert(i++, assignmentInstructions[variable]); + assignmentInstructions.Remove(variable); + variablesUnpropagated[variable.Scope].Add(variable); + } + + foreach (IRVariableBase variable in funcExternalWrites[funcRef]) + constantCache.Remove(variable); + } + } + if (instruction is ISingleOperandInstruction singleOperandInstruction) + { + if (singleOperandInstruction.Operand is IRVariable variable && + constantCache.ContainsKey(variable) && + constantCache[variable] is IRConstant) + { + singleOperandInstruction.Operand = constantCache[variable]; + } + } + else if (instruction is IMultipleOperandInstruction multipleOperandInstruction) + { + for (int j = multipleOperandInstruction.OperandCount - 1; j >= 0; j--) + { + if (multipleOperandInstruction[j] is IRVariable variable && + constantCache.ContainsKey(variable) && + constantCache[variable] is IRConstant) + { + multipleOperandInstruction[j] = constantCache[variable]; + } + } + } + } + + private class BlockDictionaries + { + public HashSet<(IRVariableBase IRVariableBase, IRAssign value)> incomingDefinitions; + public HashSet blacklist; + public BlockDictionaries( + HashSet<(IRVariableBase, IRAssign)> incomingDefinitions, + HashSet blacklist) + { + this.incomingDefinitions = incomingDefinitions; + this.blacklist = blacklist; + } + public void InitializeForPropagation( + out Dictionary constantCache, + out Dictionary assignments, + out HashSet blacklist) + { + constantCache = new Dictionary(); + assignments = new Dictionary(); + foreach ((IRVariableBase variable, IRAssign value) in incomingDefinitions) + { + constantCache.Add(variable, value.Value); + assignments.Add(variable, value); + } + blacklist = new HashSet(this.blacklist); + } + } + + private class VariableTupleEqualityComparer : IEqualityComparer<(IRVariableBase variable, IRAssign value)> + { + public static VariableTupleEqualityComparer Instance { get; } = new VariableTupleEqualityComparer(); + public bool Equals((IRVariableBase variable, IRAssign value) x, (IRVariableBase variable, IRAssign value) y) + => x.variable.Equals(y.variable) && (x.value.Value.Equals(y.value.Value)); + + public int GetHashCode((IRVariableBase variable, IRAssign value) obj) + => obj.variable.GetHashCode(); + } + } +} diff --git a/src/kOS.Safe/Compilation/Optimization/Passes/DeadCodeElimination.cs b/src/kOS.Safe/Compilation/Optimization/Passes/DeadCodeElimination.cs new file mode 100644 index 0000000000..5e6255cf3c --- /dev/null +++ b/src/kOS.Safe/Compilation/Optimization/Passes/DeadCodeElimination.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; +using System.Linq; +using kOS.Safe.Compilation.IR; + +namespace kOS.Safe.Compilation.Optimization.Passes +{ + internal class DeadCodeElimination : IHolisticOptimizationPass + { + public OptimizationLevel OptimizationLevel => OptimizationLevel.Minimal; + public short SortIndex => 50; + + public void ApplyPass(IRCodePart code) + { + HashSet rootBlocks = new HashSet(code.RootBlocks); + + RemoveDeadBlocks(code.Blocks, rootBlocks); + + foreach (IRCodePart.IRFunction function in code.Functions) + { + RemoveDeadBlocks(function.InitializationCode, rootBlocks); + foreach (IRCodePart.IRFunction.IRFunctionFragment fragment in function.Fragments) + { + RemoveDeadBlocks(fragment.FunctionCode, rootBlocks); + } + } + } + + private static void RemoveDeadBlocks(List blocks, HashSet rootBlocks) + { + List blocksToRemove = new List(); + foreach (BasicBlock block in blocks) + { + if (block.Predecessors.Any() || rootBlocks.Contains(block)) + continue; + blocksToRemove.Add(block); + } + foreach (BasicBlock block in blocksToRemove) + { + blocks.Remove(block); + foreach (BasicBlock successor in block.Successors.ToArray()) + block.RemoveSuccessor(successor); + } + } + } +} diff --git a/src/kOS.Safe/Compilation/Optimization/Passes/PeepholeOptimizations.cs b/src/kOS.Safe/Compilation/Optimization/Passes/PeepholeOptimizations.cs new file mode 100644 index 0000000000..fbe4ae204e --- /dev/null +++ b/src/kOS.Safe/Compilation/Optimization/Passes/PeepholeOptimizations.cs @@ -0,0 +1,511 @@ +using System; +using System.Collections.Generic; +using kOS.Safe.Compilation.IR; + +namespace kOS.Safe.Compilation.Optimization.Passes +{ + public class PeepholeOptimizations : IOptimizationPass + { + public OptimizationLevel OptimizationLevel => OptimizationLevel.Minimal; + + public short SortIndex => 1050; + + public void ApplyPass(List code) + { + for (int i = 0; i < code.Count; i++) + { + IRInstruction instruction = code[i]; + foreach (IRInstruction nestedInstruction in instruction.DepthFirst()) + PeepholeFilter(nestedInstruction); + + // Replace lex indexing with string constant with suffixing where possible. + if (instruction is IRIndexSet indexSet) + { + IRInstruction potentialResult = AttemptReplaceIndexSetWithSuffixSet(indexSet); + if (potentialResult != null) + code[i] = potentialResult; + } + } + } + + private static void PeepholeFilter(IRInstruction instruction) + { + // Replace parameterless suffix method calls with get member + // TODO: Skip this if clobber built-ins is active. + if (instruction is IRCall suffixCall && + !suffixCall.Direct && + suffixCall.Arguments.Count == 0 && + suffixCall.IndirectMethod is IRTemp tempSuffixCall && + tempSuffixCall.Parent is IRSuffixGetMethod) + { + ReplaceParameterlessSuffix(suffixCall); + return; + } + // Replace calls to VectorDotProduct with multiplication + // TODO: Skip this if clobber built-ins is active. + if (instruction is IRCall vDotCall && + (vDotCall.Function == "vdot" || vDotCall.Function == "vectordotproduct") && + vDotCall.Arguments.Count == 2) + { + ReplaceVectorDotProduct(vDotCall); + return; + } + // Algebraic simplifications + if (instruction is IRBinaryOp binaryOp) + { + IRTemp tempL = binaryOp.Left as IRTemp; + IRTemp tempR = binaryOp.Right as IRTemp; + switch (binaryOp.Operation) + { + case OpcodeMathAdd _: + { + // A*B + A*C = A*(B+C) + { + if (tempL != null && + tempL.Parent is IRBinaryOp opL && + tempR != null && + tempR.Parent is IRBinaryOp opR && + opL.Operation is OpcodeMathMultiply && + opR.Operation is OpcodeMathMultiply) + { + if (opR.Left == opL.Left) + { + DistributeMultiplication(binaryOp); + return; + } + if (opR.Left == opL.Right) + { + opL.SwapOperands(); + DistributeMultiplication(binaryOp); + return; + } + if (opR.Right == opL.Left) + { + opR.SwapOperands(); + DistributeMultiplication(binaryOp); + return; + } + if (opR.Right == opL.Right) + { + opL.SwapOperands(); + opR.SwapOperands(); + DistributeMultiplication(binaryOp); + return; + } + } + } + // -B+A = A+-B = A-B + { + if (tempR != null && + tempR.Parent is IRUnaryOp opR && + opR.Operation is OpcodeMathNegate) + { + DistributeNegationB(binaryOp); + return; + } + if (tempL != null && + tempL.Parent is IRUnaryOp opL && + opL.Operation is OpcodeMathNegate) + { + binaryOp.SwapOperands(); + DistributeNegationB(binaryOp); + return; + } + } + // -A+B = B-A + { + if (tempL != null && + tempL.Parent is IRUnaryOp opL && + opL.Operation is OpcodeMathNegate) + { + DistributeNegationA(binaryOp); + return; + } + } + } + break; + case OpcodeMathSubtract _: + // A--B=A+B + { + if (tempR != null && + tempR.Parent is IRUnaryOp unaryOp && + unaryOp.Operation is OpcodeMathNegate) + { + ReplaceNegateSubtract(binaryOp); + return; + } + } + break; + case OpcodeMathDivide _: + if (binaryOp.Right is IRConstant constantR && + Encapsulation.ScalarIntValue.Zero.Equals(constantR.Value)) + throw new Exceptions.KOSCompileException(instruction, new DivideByZeroException()); + // X^N/X=X^(N-1) + { + if (tempL != null && + tempL.Parent is IRBinaryOp opL && + opL.Operation is OpcodeMathPower && + opL.Left == binaryOp.Right) + { + IncreasePower(binaryOp, -1); + return; + } + } + // X^N/X^M=X^(N-M) + { + if (tempL != null && + tempL.Parent is IRBinaryOp opL && + opL.Operation is OpcodeMathPower && + tempR != null && + tempR.Parent is IRBinaryOp opR && + opR.Operation is OpcodeMathPower && + opL.Left == opR.Left) + { + DividePowers(binaryOp); + return; + } + } + break; + case OpcodeMathMultiply _: + // X^N*X=X^(N+1) + { + if (tempL != null && + tempL.Parent is IRBinaryOp opL && + opL.Operation is OpcodeMathPower && + opL.Left == binaryOp.Right) + { + IncreasePower(binaryOp, 1); + return; + } + if (tempR != null && + tempR.Parent is IRBinaryOp opR && + opR.Operation is OpcodeMathPower && + opR.Left == binaryOp.Left) + { + binaryOp.SwapOperands(); + IncreasePower(binaryOp, 1); + return; + } + } + // X*X*...*X=N^X + // Start with (X*X)*X = X*(X*X) = X^3 + // Then the previous rule will kick in and exponentiate from there + { + if (tempR != null && + tempR.Parent is IRBinaryOp opR && + opR.Operation is OpcodeMathMultiply && + opR.Left == opR.Right && + opR.Left == binaryOp.Left) + { + CreatePower(binaryOp); + return; + } + if (tempL != null && + tempL.Parent is IRBinaryOp opL && + opL.Operation is OpcodeMathMultiply && + opL.Left == opL.Right && + opL.Left == binaryOp.Right) + { + binaryOp.SwapOperands(); + CreatePower(binaryOp); + return; + } + } + // X^N*X^M=X^(N+M) + { + if (tempL != null && + tempL.Parent is IRBinaryOp opL && + opL.Operation is OpcodeMathPower && + tempR != null && + tempR.Parent is IRBinaryOp opR && + opR.Operation is OpcodeMathPower && + opL.Left == opR.Left) + { + CombinePowers(binaryOp); + return; + } + } + break; + } + } + + if (instruction is ISingleOperandInstruction singleOperandInstruction) + { + if (singleOperandInstruction.Operand is IRTemp tempOperand) + { + // Redundant unary operation replacements + // E.g. !!X = X and --X = X + if (tempOperand.Parent is IRUnaryOp unaryParent) + { + IRValue potentialResult = AttempReplaceRedundantUnaryOp(unaryParent); + if (potentialResult != null) + singleOperandInstruction.Operand = potentialResult; + } + // Replace lex indexing using string constant with suffixing where possible + if (tempOperand.Parent is IRIndexGet indexGet && + indexGet.Index is IRConstant indexConstant && + (indexConstant.Value is string || + indexConstant.Value is Encapsulation.StringValue)) + { + IRInstruction potentialResult = AttemptReplaceIndexGetWithSuffixGet(indexGet); + if (potentialResult != null) + tempOperand.Parent = potentialResult; + } + } + } + else if (instruction is IMultipleOperandInstruction multipleOperandInstruction) + { + for (int i = multipleOperandInstruction.OperandCount - 1; i >= 0; i--) + { + IRValue operand = multipleOperandInstruction[i]; + if (operand is IRTemp tempOperand) + { + // Redundant unary operation replacements + // E.g. !!X = X and --X = X + if (tempOperand.Parent is IRUnaryOp unaryParent) + { + IRValue potentialResult = AttempReplaceRedundantUnaryOp(unaryParent); + if (potentialResult != null) + multipleOperandInstruction[i] = potentialResult; + } + // Replace lex indexing using string constant with suffixing where possible + if (tempOperand.Parent is IRIndexGet indexGet) + { + IRInstruction potentialResult = AttemptReplaceIndexGetWithSuffixGet(indexGet); + if (potentialResult != null) + tempOperand.Parent = potentialResult; + } + } + } + } + // Branch logical simplification (e.g. !X branch = X branch!) + if (instruction is IRBranch branch && + branch.Condition is IRTemp temp && + temp.Parent is IRUnaryOp negateBranch && + negateBranch.Operation is OpcodeLogicNot) + { + ReplaceRedundantNotBranch(branch); + return; + } + } + + private static void ReplaceParameterlessSuffix(IRCall call) + { + IRSuffixGet suffixMethod = (IRSuffixGet)((IRTemp)call.IndirectMethod).Parent; + ((IRTemp)call.Result).Parent = new IRSuffixGet( + (IRTemp)call.Result, + suffixMethod.Object, + new OpcodeGetMember(suffixMethod.Suffix) + { + SourceLine = suffixMethod.SourceLine, + SourceColumn = suffixMethod.SourceColumn + }); + } + + private static void ReplaceVectorDotProduct(IRCall call) + { + ((IRTemp)call.Result).Parent = new IRBinaryOp( + (IRTemp)call.Result, + new OpcodeMathMultiply() + { + SourceLine = call.SourceLine, + SourceColumn = call.SourceColumn + }, + call.Arguments[0], + call.Arguments[1]); + } + + private static IRValue AttempReplaceRedundantUnaryOp(IRUnaryOp unaryParent) + { + // Replace --X with X + if (CanOperationBeReplaced(unaryParent, typeof(OpcodeMathNegate))) + return GetDoubleNestedValue(unaryParent); + // Replace !!X with X + if (CanOperationBeReplaced(unaryParent, typeof(OpcodeLogicNot))) + return GetDoubleNestedValue(unaryParent); + return null; + } + private static bool CanOperationBeReplaced(IRUnaryOp operation, Type opcodeType) + { + return operation.Operation.GetType() == opcodeType && + operation.Operand is IRTemp operand && + operand.Parent is IRUnaryOp neg2 && + neg2.Operation.GetType() == opcodeType; + } + private static IRValue GetDoubleNestedValue(ISingleOperandInstruction operation) + { + return ((IRUnaryOp)((IRTemp)operation.Operand).Parent).Operand; + } + + private static IRInstruction AttemptReplaceIndexGetWithSuffixGet(IRIndexGet indexGet) + { + if (indexGet.Index is IRConstant indexConstant && + indexConstant.Value is Encapsulation.StringValue stringIndex && + StringUtil.IsValidIdentifier(stringIndex)) + { + return new IRSuffixGet((IRTemp)indexGet.Result, + indexGet.Object, + new OpcodeGetMember(stringIndex) + { + SourceLine = indexGet.SourceLine, + SourceColumn = indexGet.SourceColumn + }); + } + return null; + } + + private static IRInstruction AttemptReplaceIndexSetWithSuffixSet(IRIndexSet indexSet) + { + if (indexSet.Index is IRConstant indexConstant && + indexConstant.Value is Encapsulation.StringValue stringIndex && + StringUtil.IsValidIdentifier(stringIndex)) + { + return new IRSuffixSet(indexSet.Object, + indexSet.Value, + new OpcodeSetMember(stringIndex) + { + SourceLine = indexSet.SourceLine, + SourceColumn = indexSet.SourceColumn + }); + } + return null; + } + + private static void ReplaceRedundantNotBranch(IRBranch branch) + { + branch.Condition = GetDoubleNestedValue(branch); + branch.PreferFalse = !branch.PreferFalse; + (branch.True, branch.False) = (branch.False, branch.True); + } + + private static void DistributeMultiplication(IRBinaryOp instruction) + { + // A*B + A*C = A*(B+C) + IRTemp tempL = (IRTemp)instruction.Left; + IRTemp tempR = (IRTemp)instruction.Right; + IRBinaryOp opL = (IRBinaryOp)tempL.Parent; + IRBinaryOp opR = (IRBinaryOp)tempR.Parent; + // Restructure to create the parentheses + opR.Operation = new OpcodeMathAdd(); + opR.Left = opL.Right; + // Restructure the multiplication term. + instruction.Left = opL.Left; + instruction.Operation = new OpcodeMathMultiply(); + } + + private static void DistributeNegationA(IRBinaryOp instruction) + { + // -A+B = B-A + IRTemp tempL = (IRTemp)instruction.Left; + IRUnaryOp opL = (IRUnaryOp)tempL.Parent; + instruction.Left = opL.Operand; + instruction.SwapOperands(); + instruction.Operation = new OpcodeMathSubtract(); + } + + private static void DistributeNegationB(IRBinaryOp instruction) + { + // A+-B = A-B + IRTemp tempR = (IRTemp)instruction.Right; + IRUnaryOp opR = (IRUnaryOp)tempR.Parent; + instruction.Right = opR.Operand; + instruction.Operation = new OpcodeMathSubtract(); + } + + private static void ReplaceNegateSubtract(IRBinaryOp instruction) + { + // A--B=A+B + IRTemp tempR = (IRTemp)instruction.Right; + IRUnaryOp opR = (IRUnaryOp)tempR.Parent; + instruction.Right = opR.Operand; + instruction.Operation = new OpcodeMathAdd(); + } + + private static void IncreasePower(IRBinaryOp instruction, int powerIncrease) + { + // X^N*X=X^(N+1) + // X^N/X=X^(N-1) + // Assumes |N +/- 1| < 2^31 - 1 (for integer scalars) or 2^53 (for double scalars) + // Going beyond that overflows an integer or the mantissa for double. + + IRTemp tempL = (IRTemp)instruction.Left; + IRBinaryOp opL = (IRBinaryOp)tempL.Parent; + + short divLine = instruction.SourceLine; + short divColumn = instruction.SourceColumn; + + IRConstant powConst = new IRConstant(new Encapsulation.ScalarIntValue(powerIncrease), instruction); + + // Set up the new power operation + instruction.Left = opL.Left; + instruction.Operation = new OpcodeMathPower(); + instruction.OverwriteSourceLocation(opL.SourceLine, opL.SourceColumn); + + // Reuse the old power instruction for the addition/subtraction + instruction.Right = tempL; + opL.Left = powConst; + opL.Operation = new OpcodeMathAdd(); + opL.OverwriteSourceLocation(divLine, divColumn); + + // Attempt constant folding afterwards + instruction.Right = ConstantFolding.AttemptReduction(opL); + + // Special case for when N == 2 afterwards + // then revert back to X * X + if (instruction.Right is IRConstant constantR && + Encapsulation.ScalarIntValue.Two.Equals(constantR.Value)) + { + instruction.Right = instruction.Left; + instruction.Operation = new OpcodeMathMultiply(); + } + } + + private static void CreatePower(IRBinaryOp instruction) + { + // X*(X*X) = X^3 + instruction.Operation = new OpcodeMathPower(); + IRTemp tempR = (IRTemp)instruction.Right; + instruction.Right = new IRConstant(new Encapsulation.ScalarIntValue(3), tempR.Parent); + } + + private static void CombinePowers(IRBinaryOp instruction) + => CombinePowers(instruction, new OpcodeMathAdd()); + private static void DividePowers(IRBinaryOp instruction) + => CombinePowers(instruction, new OpcodeMathSubtract()); + private static void CombinePowers(IRBinaryOp instruction, BinaryOpcode newOperation) + { + // X^N*X^M=X^(N+M) + // Assumes |N + M| < 2^31 - 1 (for integer scalars) or 2^53 (for double scalars) + + IRTemp tempL = (IRTemp)instruction.Left; + IRTemp tempR = (IRTemp)instruction.Right; + IRBinaryOp opL = (IRBinaryOp)tempL.Parent; + IRBinaryOp opR = (IRBinaryOp)tempR.Parent; + + short divLine = instruction.SourceLine; + short divColumn = instruction.SourceColumn; + + // Set up the new power operation + instruction.Left = opL.Left; + instruction.Operation = new OpcodeMathPower(); + instruction.OverwriteSourceLocation(opL.SourceLine, opL.SourceColumn); + + // Reuse an old power instruction for the addition/subtraction + opR.Left = opL.Right; + opR.Operation = newOperation; + opR.OverwriteSourceLocation(divLine, divColumn); + + // Attempt constant folding afterwards + instruction.Right = ConstantFolding.AttemptReduction(opR); + + // Special case for when N == 2 afterwards + // then revert back to X * X + if (instruction.Right is IRConstant constantR && + Encapsulation.ScalarIntValue.Two.Equals(constantR.Value)) + { + instruction.Right = instruction.Left; + instruction.Operation = new OpcodeMathMultiply(); + } + } + } +} diff --git a/src/kOS.Safe/Compilation/Optimization/Passes/SuffixReplacement.cs b/src/kOS.Safe/Compilation/Optimization/Passes/SuffixReplacement.cs new file mode 100644 index 0000000000..14feaa9f5c --- /dev/null +++ b/src/kOS.Safe/Compilation/Optimization/Passes/SuffixReplacement.cs @@ -0,0 +1,197 @@ +using System; +using System.Collections.Generic; +using kOS.Safe.Compilation.IR; + +namespace kOS.Safe.Compilation.Optimization.Passes +{ + public class SuffixReplacement : IOptimizationPass + { + public OptimizationLevel OptimizationLevel => OptimizationLevel.Minimal; + public short SortIndex => 10; + + public void ApplyPass(List code) + { + for (int i = 0; i < code.Count; i++) + { + IRInstruction instruction = code[i]; + switch (instruction) + { + case IRPop pop: + if (AttemptReplacement(pop.Value) is IRConstant) + { + code.RemoveAt(i); + i--; + } + continue; + case IRAssign assign: + assign.Value = AttemptReplacement(assign.Value); + break; + case IRSuffixSet suffixSet: + suffixSet.Value = AttemptReplacement(suffixSet.Value); + suffixSet.Object = AttemptReplacement(suffixSet.Object); + break; + case IRIndexSet indexSet: + indexSet.Value = AttemptReplacement(indexSet.Value); + indexSet.Object = AttemptReplacement(indexSet.Object); + indexSet.Index = AttemptReplacement(indexSet.Index); + break; + case IRBranch branch: + AttemptReplacement(branch.Condition); + break; + } + } + } + + private static IRValue AttemptReplacement(IRValue input) + { + if (input is IRTemp temp) + { + if (temp.Parent is IRSuffixGet suffixGet) + { + AttempReplaceSuffix(suffixGet); + } + else + { + AttemptReplacement(temp.Parent); + } + } + return input; + } + private static IRValue AttempReplaceSuffix(IRSuffixGet suffixGet) + { + if (suffixGet.Object is IRVariable objVariable) + { + if (objVariable.Name == "$ship") + { + switch (suffixGet.Suffix) + { + case "name": + // Rename the alias shortcut appropriately + suffixGet.Suffix = "shipname"; + break; + case "heading": + case "prograde": + case "retrograde": + case "facing": + case "maxthrust": + case "velocity": + case "geoposition": + case "latitude": + case "longitude": + case "up": + case "north": + case "body": + case "angularmomentum": + case "angularvel": + case "mass": + case "verticalSpeed": + case "groundspeed": + case "airspeed": + case "altitude": + case "apoapsis": + case "periapsis": + case "sensors": + case "srfprograde": + case "srfretrograde": + case "obt": + case "status": + break; + // All other suffixes don't have an alias, so just return the original IRTemp + default: + return suffixGet.Result; + } + // Instead of the IRTemp, which leads to resolving the suffix, return just the alias shortcut. + suffixGet.Result = new IRVariable($"${suffixGet.Suffix}", null, suffixGet); + return suffixGet.Result; + } + if (objVariable.Name == "$constant") + { + return ReplaceConstantSuffix(suffixGet); + } + } + else if (suffixGet.Object is IRTemp tempObj && tempObj.Parent is IRCall call && call.Function == "constant()" && call.Arguments.Count == 0) + { + return ReplaceConstantSuffix(suffixGet); + } + return suffixGet.Result; + } + private static IRConstant ReplaceConstantSuffix(IRSuffixGet suffixGet) + { + Optimizer.InterimCPU.PushArgumentStack(new Encapsulation.ConstantValue()); + try + { + new OpcodeGetMember(suffixGet.Suffix).Execute(Optimizer.InterimCPU); + } + catch (Exception e) + { + throw new Exceptions.KOSCompileException(suffixGet, e); + } + IRConstant result = new IRConstant(Optimizer.InterimCPU.PopValueArgument(), suffixGet); + suffixGet.Result = result; + return result; + } + private static IRValue AttemptReplacement(IRInstruction instruction) + { + switch (instruction) + { + case IRUnaryOp unaryOp: + return ReduceUnary(unaryOp); + case IRBinaryOp binaryOp: + return ReduceBinary(binaryOp); + case IRSuffixGet suffixGet: + return ReduceSuffixGet(suffixGet); + case IRIndexGet indexGet: + return ReduceIndexGet(indexGet); + case IRCall call: + return ReduceCall(call); + case IResultingInstruction resulting: + return resulting.Result; + } + throw new ArgumentException($"{instruction.GetType()} is not supported."); + } + private static IRValue ReduceUnary(IRUnaryOp instruction) + { + if (instruction.Operand is IRTemp temp) + instruction.Operand = AttemptReplacement(temp); + + return instruction.Result; + } + private static IRValue ReduceBinary(IRBinaryOp instruction) + { + if (instruction.Left is IRTemp tempL) + instruction.Left = AttemptReplacement(tempL); + if (instruction.Right is IRTemp tempR) + instruction.Right = AttemptReplacement(tempR); + + return instruction.Result; + } + + private static IRValue ReduceSuffixGet(IRSuffixGet instruction) + { + if (instruction.Object is IRTemp temp) + instruction.Object = AttemptReplacement(temp); + + return instruction.Result; + } + private static IRValue ReduceIndexGet(IRIndexGet instruction) + { + if (instruction.Object is IRTemp tempObj) + instruction.Object = AttemptReplacement(tempObj); + + if (instruction.Index is IRTemp tempIndex) + instruction.Index = AttemptReplacement(tempIndex); + + return instruction.Result; + } + private static IRValue ReduceCall(IRCall instruction) + { + for (int i = instruction.Arguments.Count - 1; i >= 0; i--) + { + if (instruction.Arguments[i] is IRTemp temp) + instruction.Arguments[i] = AttemptReplacement(temp); + } + + return instruction.Result; + } + } +} diff --git a/src/kOS.Safe/Compilation/Optimization/Passes/UnnecessaryScopeRemoval.cs b/src/kOS.Safe/Compilation/Optimization/Passes/UnnecessaryScopeRemoval.cs new file mode 100644 index 0000000000..8688466230 --- /dev/null +++ b/src/kOS.Safe/Compilation/Optimization/Passes/UnnecessaryScopeRemoval.cs @@ -0,0 +1,129 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using kOS.Safe.Compilation.IR; +using kOS.Safe.Exceptions; + +namespace kOS.Safe.Compilation.Optimization.Passes +{ + public class UnnecessaryScopeRemoval : IOptimizationPass + { + public OptimizationLevel OptimizationLevel => OptimizationLevel.Minimal; + + public short SortIndex => 32000; + + public void ApplyPass(List code) + { + HashSet scopes = new HashSet(); + HashSet removedScopes = new HashSet(); + foreach (BasicBlock block in code) + scopes.Add(block.Scope); + + foreach (IRScope scope in scopes) + { + if (scope.Variables.Any()) + continue; + + if (scope.IsGlobalScope) + continue; + + CollapseScope(scope); + + RemovePushScope(scope.HeaderBlock); + + RemovePopScope(scope.FooterBlock); + + removedScopes.Add(scope); + } + + scopes.ExceptWith(removedScopes); + foreach (BasicBlock block in code) + { + if (block.Instructions.Count > 0 && + !(block.Instructions.Last() is IRReturn)) + continue; + + AttemptPopReturnCollapse(block); + } + } + + private static void CollapseScope(IRScope scope) + { + IRScope newScope = scope.ParentScope; + short parentID = GetPushOpcode(newScope).ScopeId; + foreach (IRScope childScope in scope.Children.ToArray()) + { + childScope.ParentScope = newScope; + OpcodePushScope opcode = GetPushOpcode(childScope); + opcode.ParentScopeId = parentID; + } + foreach (BasicBlock block in scope.Blocks.ToArray()) + block.Scope = newScope; + scope.ParentScope = null; + } + private static OpcodePushScope GetPushOpcode(IRScope scope) + { + if (scope.IsGlobalScope) + return new OpcodePushScope(0, 0); + IRNoStackInstruction pushScopeInstruction = scope.HeaderBlock.Instructions[0] as IRNoStackInstruction; + return (OpcodePushScope)pushScopeInstruction.Operation; + } + + private static void RemovePushScope(BasicBlock header) + { + if (header.Instructions[0] is IRNoStackInstruction instruction && + instruction.Operation is OpcodePushScope) + header.Instructions.RemoveAt(0); + else + throw new KOSYouShouldNeverSeeThisException($"The first instruction ({header.Instructions[0]}) in scope header block {header} was not OpcodePushScope."); + } + + private static void RemovePopScope(BasicBlock footer) + { + int lastIndex = footer.Instructions.Count - 1; + IRInstruction lastInstruction = footer.Instructions[lastIndex]; + + if (lastInstruction is IRReturn ret) + { + ret.Depth -= 1; + if (ret.Depth < 0) + throw new KOSYouShouldNeverSeeThisException($"After removing unecessary scopes, the return statement in {footer} had negative depth."); + } + else if (lastInstruction is IRNoStackInstruction popInstruction && + popInstruction.Operation is OpcodePopScope) + { + footer.Instructions.RemoveAt(lastIndex); + } + else + throw new KOSYouShouldNeverSeeThisException($"The last instruction ({lastInstruction}) in scope footer block {footer} was not OpcodePopScope or OpcodeReturn."); + } + + private static void AttemptPopReturnCollapse(BasicBlock returnBlock) + { + // Don't do anything if there is more than one predecessor + int numPredecessors = returnBlock.Predecessors.Count; + if (numPredecessors == 0 || numPredecessors > 1) + return; + + BasicBlock predecessorBlock = returnBlock.Predecessors.First(); + + if (predecessorBlock.Instructions.Last() is IRNoStackInstruction popInstruction && + popInstruction.Operation is OpcodePopScope) + { + predecessorBlock.Instructions.RemoveAt(predecessorBlock.Instructions.Count - 1); + ((IRReturn)returnBlock.Instructions.Last()).Depth += 1; + returnBlock.Scope = predecessorBlock.Scope; + returnBlock.Scope.FooterBlock = returnBlock; + //BasicBlock.Merge(predecessorBlock, returnBlock); + + // Since a pop closes a BasicBlock, then if there are no instructions remaining, + // there's a chance that the pop's predecessor also ends with a pop that we can remove, + // if the pop only has a single predecessor. + if (predecessorBlock.Instructions.Count == 0) + { + AttemptPopReturnCollapse(returnBlock); + } + } + } + } +} diff --git a/src/kOS.Safe/Compilation/ProgramBuilder.cs b/src/kOS.Safe/Compilation/ProgramBuilder.cs index 14908bb4c1..1f46337730 100644 --- a/src/kOS.Safe/Compilation/ProgramBuilder.cs +++ b/src/kOS.Safe/Compilation/ProgramBuilder.cs @@ -233,8 +233,8 @@ protected virtual void AddEndOfProgram(CodePart linkedObject, bool isMainProgram } } - private void ReplaceLabels(List program) - { + public static Dictionary MapLabels(List program) + { var labels = new Dictionary(); // get the index of every label @@ -259,7 +259,13 @@ private void ReplaceLabels(List program) labels.Add(program[index].Label, index); } } - + return labels; + } + + private void ReplaceLabels(List program) + { + Dictionary labels = MapLabels(program); + // replace destination labels with the corresponding index for (int index = 0; index < program.Count; index++) { diff --git a/src/kOS.Safe/Encapsulation/Lexicon.cs b/src/kOS.Safe/Encapsulation/Lexicon.cs index e616035670..6e4edfdffe 100644 --- a/src/kOS.Safe/Encapsulation/Lexicon.cs +++ b/src/kOS.Safe/Encapsulation/Lexicon.cs @@ -11,10 +11,10 @@ namespace kOS.Safe.Encapsulation { [kOS.Safe.Utilities.KOSNomenclature("Lexicon")] - [kOS.Safe.Utilities.KOSNomenclature("Lex", CSharpToKOS = false) ] + [kOS.Safe.Utilities.KOSNomenclature("Lex", CSharpToKOS = false)] public class Lexicon : SerializableStructure, IDictionary, IIndexable { - [Function("lex", "lexicon")] + [Function("lex", "lexicon", ReturnType = typeof(Lexicon), IsInvariant = true)] public class FunctionLexicon : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) diff --git a/src/kOS.Safe/Encapsulation/ListValue.cs b/src/kOS.Safe/Encapsulation/ListValue.cs index 5650380282..bda8e2e007 100644 --- a/src/kOS.Safe/Encapsulation/ListValue.cs +++ b/src/kOS.Safe/Encapsulation/ListValue.cs @@ -150,7 +150,7 @@ public void Insert(int index, T item) [kOS.Safe.Utilities.KOSNomenclature("List", KOSToCSharp = false)] // one-way because the generic templated ListValue is the canonical one. public class ListValue : ListValue { - [Function("list")] + [Function("list", ReturnType = typeof(ListValue), IsInvariant = true)] public class FunctionList : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) diff --git a/src/kOS.Safe/Encapsulation/PIDLoop.cs b/src/kOS.Safe/Encapsulation/PIDLoop.cs index c2c36af9c9..99231a9b90 100644 --- a/src/kOS.Safe/Encapsulation/PIDLoop.cs +++ b/src/kOS.Safe/Encapsulation/PIDLoop.cs @@ -9,7 +9,7 @@ namespace kOS.Safe.Encapsulation [kOS.Safe.Utilities.KOSNomenclature("PIDLoop")] public class PIDLoop : SerializableStructure { - [Function("pidloop")] + [Function("pidloop", ReturnType = typeof(PIDLoop), IsInvariant = true)] public class PIDLoopConstructor : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) diff --git a/src/kOS.Safe/Encapsulation/QueueValue.cs b/src/kOS.Safe/Encapsulation/QueueValue.cs index 9c91ac7e3e..bf30461f8e 100644 --- a/src/kOS.Safe/Encapsulation/QueueValue.cs +++ b/src/kOS.Safe/Encapsulation/QueueValue.cs @@ -68,7 +68,7 @@ public static QueueValue CreateQueue(IEnumerable list) [kOS.Safe.Utilities.KOSNomenclature("Queue", KOSToCSharp = false)] // one-way because the generic templated QueueValue is the canonical one. public class QueueValue : QueueValue { - [Function("queue")] + [Function("queue", ReturnType = typeof(QueueValue), IsInvariant = true)] public class FunctionQueue : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) diff --git a/src/kOS.Safe/Encapsulation/StackValue.cs b/src/kOS.Safe/Encapsulation/StackValue.cs index 1eed16dae6..63b8e83d77 100644 --- a/src/kOS.Safe/Encapsulation/StackValue.cs +++ b/src/kOS.Safe/Encapsulation/StackValue.cs @@ -74,7 +74,7 @@ public static StackValue CreateStack(IEnumerable list) [kOS.Safe.Utilities.KOSNomenclature("Stack", KOSToCSharp = false)] // one-way because the generic templated StackValue is the canonical one. public class StackValue : StackValue { - [Function("stack")] + [Function("stack", ReturnType = typeof(StackValue), IsInvariant = true)] public class FunctionStack : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) diff --git a/src/kOS.Safe/Encapsulation/UniqueSetValue.cs b/src/kOS.Safe/Encapsulation/UniqueSetValue.cs index 4e0e1c0da9..e02e00effe 100644 --- a/src/kOS.Safe/Encapsulation/UniqueSetValue.cs +++ b/src/kOS.Safe/Encapsulation/UniqueSetValue.cs @@ -73,7 +73,7 @@ private void SetInitializeSuffixes() [kOS.Safe.Utilities.KOSNomenclature("UniqueSet", KOSToCSharp = false)] // one-way because the generic templated UniqueSetValue is the canonical one. public class UniqueSetValue : UniqueSetValue { - [Function("uniqueset")] + [Function("uniqueset", ReturnType = typeof(UniqueSetValue), IsInvariant = true)] public class FunctionSet : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) diff --git a/src/kOS.Safe/Exceptions/KOSCompileException.cs b/src/kOS.Safe/Exceptions/KOSCompileException.cs index 37320cae0e..4ae21be2fa 100644 --- a/src/kOS.Safe/Exceptions/KOSCompileException.cs +++ b/src/kOS.Safe/Exceptions/KOSCompileException.cs @@ -37,6 +37,11 @@ public KOSCompileException(Token token, string message) { } + public KOSCompileException(Compilation.IR.IRInstruction irInstruction, System.Exception innerException) + : this(new LineCol(irInstruction.SourceLine, irInstruction.SourceColumn), innerException.Message) + { + } + public KOSCompileException(LineCol location, string message) { Location = location; diff --git a/src/kOS.Safe/Function/FunctionAttribute.cs b/src/kOS.Safe/Function/FunctionAttribute.cs index 6fb891d874..ebe06e7392 100644 --- a/src/kOS.Safe/Function/FunctionAttribute.cs +++ b/src/kOS.Safe/Function/FunctionAttribute.cs @@ -1,11 +1,29 @@ -using System; +using System; namespace kOS.Safe.Function { public class FunctionAttribute : Attribute { public string[] Names { get; set; } - + public bool IsInvariant { get; set; } = false; + public Type ReturnType + { + get => returnType; + set + { + if (value != null && !typeof(Encapsulation.Structure).IsAssignableFrom(value)) + { +#if DEBUG + throw new ArgumentException($"{value} is not an accepted return type for a kOS function since it does not subclass {typeof(Encapsulation.Structure)}."); +#else + Utilities.SafeHouse.Logger.LogError($"The supplied type, {value}, is not an accepted return type for a kOS function since it is not a subclass of ${typeof(Encapsulation.Structure)}."); + returnType = null; +#endif + } + returnType = value; + } + } + private Type returnType = null; public FunctionAttribute(params string[] names) { Names = names; diff --git a/src/kOS.Safe/Function/FunctionManager.cs b/src/kOS.Safe/Function/FunctionManager.cs index 2c0043fb6d..443f2a55c9 100644 --- a/src/kOS.Safe/Function/FunctionManager.cs +++ b/src/kOS.Safe/Function/FunctionManager.cs @@ -10,7 +10,9 @@ namespace kOS.Safe.Function public class FunctionManager : IFunctionManager { private readonly SafeSharedObjects shared; - private Dictionary functions; + private readonly Dictionary functions = new Dictionary(StringComparer.OrdinalIgnoreCase); + private readonly Dictionary functionTypes = new Dictionary(StringComparer.OrdinalIgnoreCase); + private readonly HashSet invariantFunctions = new HashSet(); private static readonly Dictionary rawAttributes = new Dictionary(); public FunctionManager(SafeSharedObjects shared) @@ -21,7 +23,10 @@ public FunctionManager(SafeSharedObjects shared) public void Load() { - functions = new Dictionary(StringComparer.OrdinalIgnoreCase); + functions.Clear(); + functionTypes.Clear(); + invariantFunctions.Clear(); + foreach (FunctionAttribute attr in rawAttributes.Keys) { var type = rawAttributes[attr]; @@ -32,6 +37,9 @@ public void Load() if (functionName != string.Empty) { functions.Add(functionName, (SafeFunctionBase)functionObject); + functionTypes.Add(functionName, attr.ReturnType); + if (attr.IsInvariant) + invariantFunctions.Add(functionName); } } } @@ -70,5 +78,19 @@ public bool Exists(string functionName) { return functions.ContainsKey(functionName); } + + public bool IsFunctionInvariant(string functionName) + { + return invariantFunctions.Contains(functionName); + } + + public Type FunctionReturnType(string functionName) + { + if (!functions.ContainsKey(functionName)) + { + throw new Exception("Queried return type of a non-existent function " + functionName); + } + return functionTypes[functionName]; + } } } \ No newline at end of file diff --git a/src/kOS.Safe/Function/IFunctionManager.cs b/src/kOS.Safe/Function/IFunctionManager.cs index 1a243698f7..54bd58d46b 100644 --- a/src/kOS.Safe/Function/IFunctionManager.cs +++ b/src/kOS.Safe/Function/IFunctionManager.cs @@ -5,5 +5,7 @@ public interface IFunctionManager void Load(); void CallFunction(string functionName); bool Exists(string functionName); + bool IsFunctionInvariant(string functionName); + System.Type FunctionReturnType(string functionName); } } \ No newline at end of file diff --git a/src/kOS.Safe/Function/Math.cs b/src/kOS.Safe/Function/Math.cs index 554a8c062a..6ed78a4ace 100644 --- a/src/kOS.Safe/Function/Math.cs +++ b/src/kOS.Safe/Function/Math.cs @@ -4,7 +4,7 @@ namespace kOS.Safe.Function { - [Function("abs")] + [Function("abs", ReturnType = typeof(ScalarValue), IsInvariant = true)] public class FunctionAbs : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) @@ -16,7 +16,7 @@ public override void Execute(SafeSharedObjects shared) } } - [Function("mod")] + [Function("mod", ReturnType = typeof(ScalarValue), IsInvariant = true)] public class FunctionMod : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) @@ -29,7 +29,7 @@ public override void Execute(SafeSharedObjects shared) } } - [Function("floor")] + [Function("floor", ReturnType = typeof(ScalarValue), IsInvariant = true)] public class FunctionFloor : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) @@ -44,7 +44,7 @@ public override void Execute(SafeSharedObjects shared) } } - [Function("ceiling")] + [Function("ceiling", ReturnType = typeof(ScalarValue), IsInvariant = true)] public class FunctionCeiling : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) @@ -59,7 +59,7 @@ public override void Execute(SafeSharedObjects shared) } } - [Function("round")] + [Function("round", ReturnType = typeof(ScalarValue), IsInvariant = true)] public class FunctionRound : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) @@ -86,7 +86,7 @@ public override void Execute(SafeSharedObjects shared) } } - [Function("sqrt")] + [Function("sqrt", ReturnType = typeof(ScalarValue), IsInvariant = true)] public class FunctionSqrt : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) @@ -99,7 +99,7 @@ public override void Execute(SafeSharedObjects shared) } - [Function("ln")] + [Function("ln", ReturnType = typeof(ScalarValue), IsInvariant = true)] public class FunctionLn : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) @@ -111,7 +111,7 @@ public override void Execute(SafeSharedObjects shared) } } - [Function("log10")] + [Function("log10", ReturnType = typeof(ScalarValue), IsInvariant = true)] public class FunctionLog10 : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) @@ -123,7 +123,7 @@ public override void Execute(SafeSharedObjects shared) } } - [Function("min")] + [Function("min", ReturnType = typeof(ScalarValue), IsInvariant = true)] public class FunctionMin : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) @@ -153,7 +153,7 @@ public override void Execute(SafeSharedObjects shared) } } - [Function("max")] + [Function("max", ReturnType = typeof(ScalarValue), IsInvariant = true)] public class FunctionMax : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) @@ -183,7 +183,7 @@ public override void Execute(SafeSharedObjects shared) } } - [Function("random")] + [Function("random", ReturnType = typeof(ScalarDoubleValue), IsInvariant = false)] public class FunctionRandom : SafeFunctionBase { private readonly Random random = new Random(); @@ -201,7 +201,7 @@ public override void Execute(SafeSharedObjects shared) } } - [Function("randomseed")] + [Function("randomseed", ReturnType = null, IsInvariant = false)] public class FunctionRandomSeed : SafeFunctionBase { private readonly Random random = new Random(); @@ -215,7 +215,7 @@ public override void Execute(SafeSharedObjects shared) } } - [Function("char")] + [Function("char", ReturnType = typeof(StringValue), IsInvariant = true)] public class FunctionChar : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) @@ -227,7 +227,7 @@ public override void Execute(SafeSharedObjects shared) } } - [Function("unchar")] + [Function("unchar", ReturnType = typeof(ScalarIntValue), IsInvariant = true)] public class FunctionUnchar : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) diff --git a/src/kOS.Safe/Function/Misc.cs b/src/kOS.Safe/Function/Misc.cs index b0a7d64e2d..8890385070 100644 --- a/src/kOS.Safe/Function/Misc.cs +++ b/src/kOS.Safe/Function/Misc.cs @@ -10,7 +10,7 @@ namespace kOS.Safe.Function { - [Function("print")] + [Function("print", ReturnType = null, IsInvariant = false)] public class FunctionPrint : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) @@ -21,7 +21,7 @@ public override void Execute(SafeSharedObjects shared) } } - [Function("printat")] + [Function("printat", ReturnType = null, IsInvariant = false)] public class FunctionPrintAt : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) @@ -34,7 +34,7 @@ public override void Execute(SafeSharedObjects shared) } } - [Function("toggleflybywire")] + [Function("toggleflybywire", ReturnType = null, IsInvariant = false)] public class FunctionToggleFlyByWire : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) @@ -46,7 +46,7 @@ public override void Execute(SafeSharedObjects shared) } } - [Function("selectautopilotmode")] + [Function("selectautopilotmode", ReturnType = null, IsInvariant = false)] public class FunctionSelectAutopilotMode : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) @@ -57,7 +57,7 @@ public override void Execute(SafeSharedObjects shared) } } - [Function("run")] + [Function("run", ReturnType = null, IsInvariant = false)] public class FunctionRun : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) @@ -133,7 +133,7 @@ public override void Execute(SafeSharedObjects shared) } } - [FunctionAttribute("load")] + [Function("load", ReturnType = null, IsInvariant = false)] public class FunctionLoad : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) @@ -262,7 +262,7 @@ public override void Execute(SafeSharedObjects shared) } } - [Function("logfile")] + [Function("logfile", ReturnType = null, IsInvariant = false)] public class FunctionLogFile : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) @@ -300,7 +300,7 @@ public override void Execute(SafeSharedObjects shared) } } - [Function("reboot")] + [Function("reboot", ReturnType = null, IsInvariant = false)] public class FunctionReboot : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) @@ -315,7 +315,7 @@ public override void Execute(SafeSharedObjects shared) } } - [Function("shutdown")] + [Function("shutdown", ReturnType = null, IsInvariant = false)] public class FunctionShutdown : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) @@ -326,7 +326,7 @@ public override void Execute(SafeSharedObjects shared) } } - [Function("debugdump")] + [Function("debugdump", ReturnType = null, IsInvariant = false)] public class DebugDump : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) @@ -336,7 +336,7 @@ public override void Execute(SafeSharedObjects shared) } } - [Function("debugfreezegame")] + [Function("debugfreezegame", ReturnType = null, IsInvariant = false)] /// /// Deliberately cause physics lag by making the main game thread sleep. /// Clearly not something there's a good reason to do *except* when @@ -361,7 +361,7 @@ public override void Execute(SafeSharedObjects shared) } } - [Function("profileresult")] + [Function("profileresult", ReturnType = typeof(StringValue), IsInvariant = false)] public class ProfileResult : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) @@ -383,7 +383,7 @@ public override void Execute(SafeSharedObjects shared) } } - [Function("makebuiltindelegate")] + [Function("makebuiltindelegate", ReturnType = typeof(BuiltinDelegate), IsInvariant = false)] public class MakeBuiltinDelegate : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) @@ -395,7 +395,7 @@ public override void Execute(SafeSharedObjects shared) } } - [Function("droppriority")] + [Function("droppriority", ReturnType = null, IsInvariant = false)] public class AllowInterrupt : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) diff --git a/src/kOS.Safe/Function/Persistence.cs b/src/kOS.Safe/Function/Persistence.cs index 59a248e969..90b1b4aa6d 100644 --- a/src/kOS.Safe/Function/Persistence.cs +++ b/src/kOS.Safe/Function/Persistence.cs @@ -12,7 +12,7 @@ namespace kOS.Safe.Function * remove these function below as well any metions of delete/rename file/rename volume/copy from kRISC.tpg in the future. */ - [Function("copy_deprecated")] + [Function("copy_deprecated", ReturnType = null, IsInvariant = false)] public class FunctionCopyDeprecated : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) @@ -49,7 +49,7 @@ public override void Execute(SafeSharedObjects shared) } } - [Function("rename_file_deprecated")] + [Function("rename_file_deprecated", ReturnType = null, IsInvariant = false)] public class FunctionRenameFileDeprecated : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) @@ -76,7 +76,7 @@ public override void Execute(SafeSharedObjects shared) } } - [Function("rename_volume_deprecated")] + [Function("rename_volume_deprecated", ReturnType = null, IsInvariant = false)] public class FunctionRenameVolumeDeprecated : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) @@ -99,7 +99,7 @@ public override void Execute(SafeSharedObjects shared) } } - [Function("delete_deprecated")] + [Function("delete_deprecated", ReturnType = null, IsInvariant = false)] public class FunctionDeleteDeprecated : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) @@ -127,7 +127,7 @@ public override void Execute(SafeSharedObjects shared) } } - [Function("scriptpath")] + [Function("scriptpath", ReturnType = typeof(PathValue), IsInvariant = false)] public class FunctionScriptPath : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) @@ -141,7 +141,7 @@ public override void Execute(SafeSharedObjects shared) } } - [Function("switch")] + [Function("switch", ReturnType = null, IsInvariant = false)] public class FunctionSwitch : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) @@ -164,7 +164,7 @@ public override void Execute(SafeSharedObjects shared) } } - [Function("cd", "chdir")] + [Function("cd", "chdir", ReturnType = null, IsInvariant = false)] public class FunctionCd : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) @@ -198,7 +198,7 @@ public override void Execute(SafeSharedObjects shared) } } - [Function("copypath")] + [Function("copypath", ReturnType = typeof(BooleanValue), IsInvariant = false)] public class FunctionCopyPath : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) @@ -214,7 +214,7 @@ public override void Execute(SafeSharedObjects shared) } } - [Function("movepath")] + [Function("movepath", ReturnType = typeof(BooleanValue), IsInvariant = false)] public class FunctionMove : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) @@ -230,7 +230,7 @@ public override void Execute(SafeSharedObjects shared) } } - [Function("deletepath")] + [Function("deletepath", ReturnType = typeof(BooleanValue), IsInvariant = false)] public class FunctionDeletePath : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) @@ -245,7 +245,7 @@ public override void Execute(SafeSharedObjects shared) } } - [Function("writejson")] + [Function("writejson", ReturnType = typeof(VolumeFile), IsInvariant = false)] public class FunctionWriteJson : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) @@ -270,7 +270,7 @@ public override void Execute(SafeSharedObjects shared) } } - [Function("readjson")] + [Function("readjson", ReturnType = typeof(Structure), IsInvariant = false)] public class FunctionReadJson : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) @@ -293,7 +293,7 @@ public override void Execute(SafeSharedObjects shared) } } - [Function("exists")] + [Function("exists", ReturnType = typeof(BooleanValue), IsInvariant = false)] public class FunctionExists : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) @@ -308,7 +308,7 @@ public override void Execute(SafeSharedObjects shared) } } - [Function("open")] + [Function("open", ReturnType = typeof(Structure), IsInvariant = false)] public class FunctionOpen : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) @@ -328,7 +328,7 @@ public override void Execute(SafeSharedObjects shared) } } - [Function("create")] + [Function("create", ReturnType = typeof(VolumeFile), IsInvariant = false)] public class FunctionCreate : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) @@ -345,7 +345,7 @@ public override void Execute(SafeSharedObjects shared) } } - [Function("createdir")] + [Function("createdir", ReturnType = typeof(VolumeDirectory), IsInvariant = false)] public class FunctionCreateDirectory : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) diff --git a/src/kOS.Safe/Function/Suffixed.cs b/src/kOS.Safe/Function/Suffixed.cs index 3367b373d6..ffe94b966c 100644 --- a/src/kOS.Safe/Function/Suffixed.cs +++ b/src/kOS.Safe/Function/Suffixed.cs @@ -3,7 +3,7 @@ namespace kOS.Safe.Function { - [Function("range")] + [Function("range", ReturnType = typeof(RangeValue), IsInvariant = true)] public class FunctionRange : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) @@ -38,7 +38,7 @@ public override void Execute(SafeSharedObjects shared) } } - [Function("constant")] + [Function("constant", ReturnType = typeof(ConstantValue), IsInvariant = true)] public class FunctionConstant : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) diff --git a/src/kOS.Safe/Function/Trigonometry.cs b/src/kOS.Safe/Function/Trigonometry.cs index f8e53cd512..c362c9a132 100644 --- a/src/kOS.Safe/Function/Trigonometry.cs +++ b/src/kOS.Safe/Function/Trigonometry.cs @@ -1,10 +1,10 @@ -using System; +using System; using kOS.Safe.Encapsulation; using kOS.Safe.Utilities; namespace kOS.Safe.Function { - [Function("sin")] + [Function("sin", ReturnType = typeof(ScalarDoubleValue), IsInvariant = true)] public class FunctionSin : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) @@ -17,7 +17,7 @@ public override void Execute(SafeSharedObjects shared) } } - [Function("cos")] + [Function("cos", ReturnType = typeof(ScalarDoubleValue), IsInvariant = true)] public class FunctionCos : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) @@ -30,7 +30,7 @@ public override void Execute(SafeSharedObjects shared) } } - [Function("tan")] + [Function("tan", ReturnType = typeof(ScalarDoubleValue), IsInvariant = true)] public class FunctionTan : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) @@ -43,7 +43,7 @@ public override void Execute(SafeSharedObjects shared) } } - [Function("arcsin")] + [Function("arcsin", ReturnType = typeof(ScalarDoubleValue), IsInvariant = true)] public class FunctionArcSin : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) @@ -55,7 +55,7 @@ public override void Execute(SafeSharedObjects shared) } } - [Function("arccos")] + [Function("arccos", ReturnType = typeof(ScalarDoubleValue), IsInvariant = true)] public class FunctionArcCos : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) @@ -67,7 +67,7 @@ public override void Execute(SafeSharedObjects shared) } } - [Function("arctan")] + [Function("arctan", ReturnType = typeof(ScalarDoubleValue), IsInvariant = true)] public class FunctionArcTan : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) @@ -79,7 +79,7 @@ public override void Execute(SafeSharedObjects shared) } } - [Function("arctan2")] + [Function("arctan2", ReturnType = typeof(ScalarDoubleValue), IsInvariant = true)] public class FunctionArcTan2 : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) @@ -92,7 +92,7 @@ public override void Execute(SafeSharedObjects shared) } } - [Function("anglediff")] + [Function("anglediff", ReturnType = typeof(ScalarDoubleValue), IsInvariant = true)] public class FunctionAngleDiff : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) diff --git a/src/kOS.Safe/Persistence/PathValue.cs b/src/kOS.Safe/Persistence/PathValue.cs index 6c10d9c0ce..2a53bac29c 100644 --- a/src/kOS.Safe/Persistence/PathValue.cs +++ b/src/kOS.Safe/Persistence/PathValue.cs @@ -18,7 +18,7 @@ namespace kOS.Safe [kOS.Safe.Utilities.KOSNomenclature("Path")] public class PathValue : SerializableStructure { - [Function("path")] + [Function("path", ReturnType = typeof(PathValue), IsInvariant = false)] public class FunctionPath : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) diff --git a/src/kOS.Safe/Persistence/Volume.cs b/src/kOS.Safe/Persistence/Volume.cs index d4127657ff..4d22ac86f7 100644 --- a/src/kOS.Safe/Persistence/Volume.cs +++ b/src/kOS.Safe/Persistence/Volume.cs @@ -10,7 +10,7 @@ namespace kOS.Safe.Persistence [kOS.Safe.Utilities.KOSNomenclature("Volume")] public abstract class Volume : Structure { - [Function("volume")] + [Function("volume", ReturnType = typeof(Volume), IsInvariant = false)] public class FunctionVolume : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) diff --git a/src/kOS/AddOns/KerbalAlarmClock/KACFunctions.cs b/src/kOS/AddOns/KerbalAlarmClock/KACFunctions.cs index 30e1e64853..b9b4fb3248 100644 --- a/src/kOS/AddOns/KerbalAlarmClock/KACFunctions.cs +++ b/src/kOS/AddOns/KerbalAlarmClock/KACFunctions.cs @@ -1,4 +1,4 @@ -using kOS.Function; +using kOS.Function; using kOS.Safe.Encapsulation; using kOS.Safe.Exceptions; using kOS.Safe.Function; @@ -8,7 +8,7 @@ namespace kOS.AddOns.KerbalAlarmClock { - [Function("addAlarm")] + [Function("addAlarm", ReturnType = typeof(Structure), IsInvariant = false)] public class FunctionAddAlarm : FunctionBase { public override void Execute(SharedObjects shared) @@ -66,12 +66,12 @@ public override void Execute(SharedObjects shared) } } - [Function("listAlarms")] + [Function("listAlarms", ReturnType = typeof(ListValue), IsInvariant = false)] public class FunctionListAlarms : FunctionBase { public override void Execute(SharedObjects shared) { - var list = new ListValue(); + var list = new ListValue(); string alarmTypes = PopValueAssert(shared).ToString(); AssertArgBottomAndConsume(shared); @@ -100,7 +100,7 @@ public override void Execute(SharedObjects shared) } } - [Function("deleteAlarm")] + [Function("deleteAlarm", ReturnType = typeof(BooleanValue), IsInvariant = false)] public class FunctionDeleteAlarm : FunctionBase { public override void Execute(SharedObjects shared) diff --git a/src/kOS/Function/BuildList.cs b/src/kOS/Function/BuildList.cs index 84376baff6..3305a3ee58 100644 --- a/src/kOS/Function/BuildList.cs +++ b/src/kOS/Function/BuildList.cs @@ -9,7 +9,7 @@ namespace kOS.Function { - [Function("buildlist")] + [Function("buildlist", ReturnType = typeof(ListValue), IsInvariant = false)] public class FunctionBuildList : FunctionBase { public override void Execute(SharedObjects shared) diff --git a/src/kOS/Function/Math.cs b/src/kOS/Function/Math.cs index 8850e087e7..ddf6b5998a 100644 --- a/src/kOS/Function/Math.cs +++ b/src/kOS/Function/Math.cs @@ -1,4 +1,4 @@ -using System; +using System; using kOS.Safe.Compilation; using kOS.Safe.Function; using kOS.Suffixed; @@ -7,7 +7,7 @@ namespace kOS.Function { - [Function("vcrs", "vectorcrossproduct")] + [Function("vcrs", "vectorcrossproduct", ReturnType = typeof(Vector), IsInvariant = true)] public class FunctionVectorCross : FunctionBase { public override void Execute(SharedObjects shared) @@ -26,7 +26,7 @@ public override void Execute(SharedObjects shared) } } - [Function("vdot", "vectordotproduct")] + [Function("vdot", "vectordotproduct", ReturnType = typeof(ScalarValue), IsInvariant = true)] public class FunctionVectorDot : FunctionBase { public override void Execute(SharedObjects shared) @@ -45,7 +45,7 @@ public override void Execute(SharedObjects shared) } } - [Function("vxcl", "vectorexclude")] + [Function("vxcl", "vectorexclude", ReturnType = typeof(Vector), IsInvariant = true)] public class FunctionVectorExclude : FunctionBase { public override void Execute(SharedObjects shared) @@ -64,7 +64,7 @@ public override void Execute(SharedObjects shared) } } - [Function("vang", "vectorangle")] + [Function("vang", "vectorangle", ReturnType = typeof(ScalarValue), IsInvariant = true)] public class FunctionVectorAngle : FunctionBase { public override void Execute(SharedObjects shared) diff --git a/src/kOS/Function/Misc.cs b/src/kOS/Function/Misc.cs index eb32c6619f..e090c09b49 100644 --- a/src/kOS/Function/Misc.cs +++ b/src/kOS/Function/Misc.cs @@ -19,7 +19,7 @@ namespace kOS.Function { - [Function("clearscreen")] + [Function("clearscreen", ReturnType = null, IsInvariant = false)] public class FunctionClearScreen : FunctionBase { public override void Execute(SharedObjects shared) @@ -29,7 +29,7 @@ public override void Execute(SharedObjects shared) } } - [Function("hudtext")] + [Function("hudtext", ReturnType = null, IsInvariant = false)] public class FunctionHudText : FunctionBase { public override void Execute(SharedObjects shared) @@ -71,7 +71,7 @@ public override void Execute(SharedObjects shared) } } - [Function("stage")] + [Function("stage", ReturnType = null, IsInvariant = false)] public class FunctionStage : FunctionBase { public override void Execute(SharedObjects shared) @@ -93,7 +93,7 @@ public override void Execute(SharedObjects shared) } } - [Function("add")] + [Function("add", ReturnType = null, IsInvariant = false)] public class FunctionAddNode : FunctionBase { public override void Execute(SharedObjects shared) @@ -104,7 +104,7 @@ public override void Execute(SharedObjects shared) } } - [Function("remove")] + [Function("remove", ReturnType = null, IsInvariant = false)] public class FunctionRemoveNode : FunctionBase { public override void Execute(SharedObjects shared) @@ -115,7 +115,7 @@ public override void Execute(SharedObjects shared) } } - [Function("warpto")] + [Function("warpto", ReturnType = null, IsInvariant = false)] public class WarpTo : FunctionBase { public override void Execute(SharedObjects shared) @@ -137,7 +137,7 @@ public override void Execute(SharedObjects shared) } } - [Function("processor")] + [Function("processor", ReturnType = typeof(PartModuleFields), IsInvariant = false)] public class FunctionProcessor : FunctionBase { public override void Execute(SharedObjects shared) diff --git a/src/kOS/Function/Persistence.cs b/src/kOS/Function/Persistence.cs index 4b21299740..8b1ef6e50d 100644 --- a/src/kOS/Function/Persistence.cs +++ b/src/kOS/Function/Persistence.cs @@ -1,4 +1,4 @@ -using kOS.Safe.Encapsulation; +using kOS.Safe.Encapsulation; using kOS.Safe.Exceptions; using kOS.Safe.Function; using kOS.Safe.Persistence; @@ -13,7 +13,7 @@ namespace kOS.Function { - [Function("edit")] + [Function("edit", ReturnType = null, IsInvariant = false)] public class FunctionEdit : FunctionBase { public override void Execute(SharedObjects shared) diff --git a/src/kOS/Function/PrintList.cs b/src/kOS/Function/PrintList.cs index 0667db142c..e36d88ba6c 100644 --- a/src/kOS/Function/PrintList.cs +++ b/src/kOS/Function/PrintList.cs @@ -16,7 +16,7 @@ namespace kOS.Function { - [Function("printlist")] + [Function("printlist", ReturnType = null, IsInvariant = false)] public class FunctionPrintList : FunctionBase { public override void Execute(SharedObjects shared) diff --git a/src/kOS/Function/Suffixed.cs b/src/kOS/Function/Suffixed.cs index abf577fe28..e918f251e5 100644 --- a/src/kOS/Function/Suffixed.cs +++ b/src/kOS/Function/Suffixed.cs @@ -15,7 +15,7 @@ namespace kOS.Function { - [Function("node")] + [Function("node", ReturnType = typeof(Node), IsInvariant = false)] public class FunctionNode : FunctionBase { public override void Execute(SharedObjects shared) @@ -40,7 +40,7 @@ public override void Execute(SharedObjects shared) } } - [Function("v")] + [Function("v", ReturnType = typeof(Vector), IsInvariant = true)] public class FunctionVector : FunctionBase { public override void Execute(SharedObjects shared) @@ -55,7 +55,7 @@ public override void Execute(SharedObjects shared) } } - [Function("r")] + [Function("r", ReturnType = typeof(Direction), IsInvariant = true)] public class FunctionRotation : FunctionBase { public override void Execute(SharedObjects shared) @@ -70,7 +70,7 @@ public override void Execute(SharedObjects shared) } } - [Function("q")] + [Function("q", ReturnType = typeof(Direction), IsInvariant = true)] public class FunctionQuaternion : FunctionBase { public override void Execute(SharedObjects shared) @@ -86,7 +86,7 @@ public override void Execute(SharedObjects shared) } } - [Function("createOrbit")] + [Function("createOrbit", ReturnType = typeof(OrbitInfo), IsInvariant = false)] public class FunctionCreateOrbit : FunctionBase { public override void Execute(SharedObjects shared) @@ -131,7 +131,7 @@ public override void Execute(SharedObjects shared) } } - [Function("rotatefromto")] + [Function("rotatefromto", ReturnType = typeof(Direction), IsInvariant = true)] public class FunctionRotateFromTo : FunctionBase { public override void Execute(SharedObjects shared) @@ -145,7 +145,7 @@ public override void Execute(SharedObjects shared) } } - [Function("lookdirup")] + [Function("lookdirup", ReturnType = typeof(Direction), IsInvariant = true)] public class FunctionLookDirUp : FunctionBase { public override void Execute(SharedObjects shared) @@ -159,7 +159,7 @@ public override void Execute(SharedObjects shared) } } - [Function("angleaxis")] + [Function("angleaxis", ReturnType = typeof(Direction), IsInvariant = true)] public class FunctionAngleAxis : FunctionBase { public override void Execute(SharedObjects shared) @@ -173,7 +173,7 @@ public override void Execute(SharedObjects shared) } } - [Function("latlng")] + [Function("latlng", ReturnType = typeof(GeoCoordinates), IsInvariant = false)] public class FunctionLatLng : FunctionBase { public override void Execute(SharedObjects shared) @@ -187,7 +187,7 @@ public override void Execute(SharedObjects shared) } } - [Function("vessel")] + [Function("vessel", ReturnType = typeof(VesselTarget), IsInvariant = false)] public class FunctionVessel : FunctionBase { public override void Execute(SharedObjects shared) @@ -199,7 +199,7 @@ public override void Execute(SharedObjects shared) } } - [Function("body")] + [Function("body", ReturnType = typeof(BodyTarget), IsInvariant = false)] public class FunctionBody : FunctionBase { public override void Execute(SharedObjects shared) @@ -211,7 +211,7 @@ public override void Execute(SharedObjects shared) } } - [Function("bodyexists")] + [Function("bodyexists", ReturnType = typeof(BooleanValue), IsInvariant = false)] public class FunctionBodyExists : FunctionBase { public override void Execute(SharedObjects shared) @@ -222,7 +222,7 @@ public override void Execute(SharedObjects shared) } } - [Function("bodyatmosphere")] + [Function("bodyatmosphere", ReturnType = typeof(BodyAtmosphere), IsInvariant = false)] public class FunctionBodyAtmosphere : FunctionBase { public override void Execute(SharedObjects shared) @@ -237,7 +237,7 @@ public override void Execute(SharedObjects shared) } } - [Function("bounds")] + [Function("bounds", ReturnType = typeof(BoundsValue), IsInvariant = false)] public class FunctionBounds : FunctionBase { public override void Execute(SharedObjects shared) @@ -252,7 +252,7 @@ public override void Execute(SharedObjects shared) } } - [Function("heading")] + [Function("heading", ReturnType = typeof(Direction), IsInvariant = false)] public class FunctionHeading : FunctionBase { public override void Execute(SharedObjects shared) @@ -274,7 +274,7 @@ public override void Execute(SharedObjects shared) } } - [Function("slidenote")] + [Function("slidenote", ReturnType = typeof(NoteValue), IsInvariant = true)] public class FunctionSlideNote : FunctionBase { public override void Execute(SharedObjects shared) @@ -304,7 +304,7 @@ public override void Execute(SharedObjects shared) } } - [Function("note")] + [Function("note", ReturnType = typeof(NoteValue), IsInvariant = true)] public class FunctionNote : FunctionBase { public override void Execute(SharedObjects shared) @@ -333,7 +333,7 @@ public override void Execute(SharedObjects shared) } } - [Function("GetVoice")] + [Function("GetVoice", ReturnType = typeof(VoiceValue), IsInvariant = false)] public class FunctionGetVoice : FunctionBase { public override void Execute(SharedObjects shared) @@ -354,7 +354,7 @@ public override void Execute(SharedObjects shared) } - [Function("StopAllVoices")] + [Function("StopAllVoices", ReturnType = null, IsInvariant = false)] public class FunctionStopAllVoices : FunctionBase { public override void Execute(SharedObjects shared) @@ -362,7 +362,7 @@ public override void Execute(SharedObjects shared) shared.SoundMaker.StopAllVoices(); } } - [Function("timestamp", "time")] + [Function("timestamp", "time", ReturnType = typeof(TimeStamp), IsInvariant = false)] public class FunctionTimeStamp : FunctionBase { // Note: "TIME" is both a bound variable AND a built-in function now. @@ -426,7 +426,7 @@ public override void Execute(SharedObjects shared) } } - [Function("timespan")] + [Function("timespan", ReturnType = typeof(Suffixed.TimeSpan), IsInvariant = true)] public class FunctionTimeSpan : FunctionBase { public override void Execute(SharedObjects shared) @@ -477,7 +477,7 @@ public override void Execute(SharedObjects shared) } } - [Function("hsv")] + [Function("hsv", ReturnType = typeof(HsvaColor), IsInvariant = true)] public class FunctionHsv : FunctionBase { public override void Execute(SharedObjects shared) @@ -490,7 +490,7 @@ public override void Execute(SharedObjects shared) } } - [Function("hsva")] + [Function("hsva", ReturnType = typeof(HsvaColor), IsInvariant = true)] public class FunctionHsva : FunctionBase { public override void Execute(SharedObjects shared) @@ -504,7 +504,7 @@ public override void Execute(SharedObjects shared) } } - [Function("rgb")] + [Function("rgb", ReturnType = typeof(RgbaColor), IsInvariant = true)] public class FunctionRgb : FunctionBase { public override void Execute(SharedObjects shared) @@ -517,7 +517,7 @@ public override void Execute(SharedObjects shared) } } - [Function("rgba")] + [Function("rgba", ReturnType = typeof(RgbaColor), IsInvariant = true)] public class FunctionRgba : FunctionBase { public override void Execute(SharedObjects shared) @@ -539,7 +539,7 @@ public override void Execute(SharedObjects shared) // Note: vecdraw now counts the args and changes its behavior accordingly. // For backward compatibility, vecdrawargs has been aliased to vecdraw. // - [Function("vecdraw", "vecdrawargs")] + [Function("vecdraw", "vecdrawargs", ReturnType = typeof(VectorRenderer), IsInvariant = false)] public class FunctionVecDrawNull : FunctionBase { protected RgbaColor GetDefaultColor() @@ -629,7 +629,7 @@ public void DoExecuteWork( } } - [Function("clearvecdraws")] + [Function("clearvecdraws", ReturnType = null, IsInvariant = false)] public class FunctionHideAllVecdraws : FunctionBase { public override void Execute(SharedObjects shared) @@ -639,7 +639,7 @@ public override void Execute(SharedObjects shared) } } - [Function("clearguis")] + [Function("clearguis", ReturnType = null, IsInvariant = false)] public class FunctionClearAllGuis : FunctionBase { public override void Execute(SharedObjects shared) @@ -649,7 +649,7 @@ public override void Execute(SharedObjects shared) } } - [Function("gui")] + [Function("gui", ReturnType = typeof(GUIWidgets), IsInvariant = false)] public class FunctionWidgets : FunctionBase { public override void Execute(SharedObjects shared) @@ -662,7 +662,7 @@ public override void Execute(SharedObjects shared) } } - [Function("positionat")] + [Function("positionat", ReturnType = typeof(Vector), IsInvariant = false)] public class FunctionPositionAt : FunctionBase { public override void Execute(SharedObjects shared) @@ -675,7 +675,7 @@ public override void Execute(SharedObjects shared) } } - [Function("velocityat")] + [Function("velocityat", ReturnType = typeof(Vector), IsInvariant = false)] public class FunctionVelocityAt : FunctionBase { public override void Execute(SharedObjects shared) @@ -688,7 +688,7 @@ public override void Execute(SharedObjects shared) } } - [Function("highlight")] + [Function("highlight", ReturnType = typeof(HighlightStructure), IsInvariant = false)] public class FunctionHightlight : FunctionBase { public override void Execute(SharedObjects shared) @@ -701,7 +701,7 @@ public override void Execute(SharedObjects shared) } } - [Function("orbitat")] + [Function("orbitat", ReturnType = typeof(OrbitInfo), IsInvariant = false)] public class FunctionOrbitAt : FunctionBase { public override void Execute(SharedObjects shared) @@ -714,7 +714,7 @@ public override void Execute(SharedObjects shared) } } - [Function("career")] + [Function("career", ReturnType = typeof(Career), IsInvariant = false)] public class FunctionCareer : FunctionBase { public override void Execute(SharedObjects shared) @@ -724,7 +724,7 @@ public override void Execute(SharedObjects shared) } } - [Function("allwaypoints")] + [Function("allwaypoints", ReturnType = typeof(ListValue), IsInvariant = false)] public class FunctionAllWaypoints : FunctionBase { public override void Execute(SharedObjects shared) @@ -732,7 +732,7 @@ public override void Execute(SharedObjects shared) AssertArgBottomAndConsume(shared); // no args // ReSharper disable SuggestUseVarKeywordEvident - ListValue returnList = new ListValue(); + ListValue returnList = new ListValue(); // ReSharper enable SuggestUseVarKeywordEvident WaypointManager wpm = WaypointManager.Instance(); @@ -758,7 +758,7 @@ public override void Execute(SharedObjects shared) } } - [Function("waypoint")] + [Function("waypoint", ReturnType = typeof(WaypointValue), IsInvariant = false)] public class FunctionWaypoint : FunctionBase { public override void Execute(SharedObjects shared) @@ -805,7 +805,7 @@ public override void Execute(SharedObjects shared) } } - [Function("transferall")] + [Function("transferall", ReturnType = typeof(ResourceTransferValue), IsInvariant = false)] public class FunctionTransferAll : FunctionBase { public override void Execute(SharedObjects shared) @@ -828,7 +828,7 @@ public override void Execute(SharedObjects shared) } - [Function("transfer")] + [Function("transfer", ReturnType = typeof(ResourceTransferValue), IsInvariant = false)] public class FunctionTransfer : FunctionBase { public override void Execute(SharedObjects shared) diff --git a/src/kOS/Screen/Interpreter.cs b/src/kOS/Screen/Interpreter.cs index 997fc3d201..41acd0f2fe 100644 --- a/src/kOS/Screen/Interpreter.cs +++ b/src/kOS/Screen/Interpreter.cs @@ -140,7 +140,8 @@ protected void CompileCommand(string commandText) FuncManager = Shared.FunctionManager, BindManager = Shared.BindingMgr, AllowClobberBuiltins = SafeHouse.Config.AllowClobberBuiltIns, - IsCalledFromRun = false + IsCalledFromRun = false, + OptimizationLevel = OptimizationLevel.None }; List commandParts = Shared.ScriptHandler.Compile(new InterpreterPath(this),