protected IEnumerable <Opcode> BuildBoilerplateLoader() { List <Opcode> boilerplate = new List <Opcode>(); InternalPath path = new BuiltInPath(); // First label of the load/runner will be called "@LR00", which is important because that's // what we hardcode all the compiler-built VisitRunStatement's to call out to: labelCounter = 0; labelFormat = "@LR{0:D2}"; boilerplate.Add(new OpcodePushScope(-999, 0) { Label = nextLabel, SourcePath = path }); // High level kerboscript function calls flip the argument orders for us, but // low level kRISC does not so the parameters have to be read in stack order: // store parameter 2 in a local name: boilerplate.Add(new OpcodeStoreLocal("$runonce") { Label = nextLabel, SourcePath = path }); // store parameter 1 in a local name: boilerplate.Add(new OpcodeStoreLocal("$filename") { Label = nextLabel, SourcePath = path }); // Unconditionally call load() no matter what. load() will abort and return // early if the program was already compiled, and tell us that on the stack: boilerplate.Add(new OpcodePush(new kOS.Safe.Execution.KOSArgMarkerType()) { Label = nextLabel, SourcePath = path }); boilerplate.Add(new OpcodePush("$filename") { Label = nextLabel, SourcePath = path }); boilerplate.Add(new OpcodeEval() { Label = nextLabel, SourcePath = path }); boilerplate.Add(new OpcodePush(true) { Label = nextLabel, SourcePath = path }); // the flag that tells load() to abort early if it's already loaded: boilerplate.Add(new OpcodePush(null) { Label = nextLabel, SourcePath = path }); boilerplate.Add(new OpcodeCall("load()") { Label = nextLabel, SourcePath = path }); // Stack now has the 2 return values of load(): // Topmost value is a boolean flag for whether or not the program was already loaded. // Second-from-top value is the entry point into the program. // If load() didn't claim the program was already loaded, or if we aren't operating // in "once" mode, then jump to the part where we call the loaded program, else // fall through to a dummy do-nothing return for the "run once, but it already ran" case: Opcode branchFromOne = new OpcodeBranchIfFalse() { Label = nextLabel, SourcePath = path }; boilerplate.Add(branchFromOne); boilerplate.Add(new OpcodePush("$runonce") { Label = nextLabel, SourcePath = path }); Opcode branchFromTwo = new OpcodeBranchIfFalse() { Label = nextLabel, SourcePath = path }; boilerplate.Add(branchFromTwo); boilerplate.Add(new OpcodePop() { Label = nextLabel, SourcePath = path }); // onsume the entry point that load() returned. We won't be calling it. boilerplate.Add(new OpcodePush(0) { Label = nextLabel, SourcePath = path }); // ---+-- The dummy do-nothing return. boilerplate.Add(new OpcodeReturn(1) { Label = nextLabel, SourcePath = path }); // ---' // Actually call the Program from its entry Point, which is now the thing left on top // of the stack from the second return value of load(): Opcode branchTo = new OpcodeStoreLocal("$entrypoint") { Label = nextLabel, SourcePath = path }; boilerplate.Add(branchTo); boilerplate.Add(new OpcodeCall("$entrypoint") { Label = nextLabel, SourcePath = path }); boilerplate.Add(new OpcodePop() { Label = nextLabel, SourcePath = path }); boilerplate.Add(new OpcodePush(0) { Label = nextLabel, SourcePath = path }); boilerplate.Add(new OpcodeReturn(1) { Label = nextLabel, SourcePath = path }); branchFromOne.DestinationLabel = branchTo.Label; branchFromTwo.DestinationLabel = branchTo.Label; return(boilerplate); }
private void PreProcessRunStatement(ParseNode node) { if (_options.LoadProgramsInSameAddressSpace) { bool hasON = node.Nodes.Any(cn => cn.Token.Type == TokenType.ON); if (!hasON) { string subprogramName = node.Nodes[1].Token.Text; if (!_context.Subprograms.Contains(subprogramName)) { Subprogram subprogramObject = _context.Subprograms.GetSubprogram(subprogramName); // Function code _currentCodeSection = subprogramObject.FunctionCode; // verify if the program has been loaded Opcode functionStart = AddOpcode(new OpcodePush(subprogramObject.PointerIdentifier)); AddOpcode(new OpcodePush(0)); AddOpcode(new OpcodeCompareEqual()); OpcodeBranchIfFalse branchOpcode = new OpcodeBranchIfFalse(); AddOpcode(branchOpcode); // if it wasn't then load it now AddOpcode(new OpcodePush(subprogramObject.PointerIdentifier)); AddOpcode(new OpcodePush(subprogramObject.SubprogramName)); AddOpcode(new OpcodeCall("load()")); // store the address of the program in the pointer variable // (the load() function pushes the address onto the stack) AddOpcode(new OpcodeStore()); // call the program Opcode callOpcode = AddOpcode(new OpcodeCall(subprogramObject.PointerIdentifier)); // set the call opcode as the destination of the previous branch branchOpcode.DestinationLabel = callOpcode.Label; // return to the caller address AddOpcode(new OpcodeReturn()); // set the function start label subprogramObject.FunctionLabel = functionStart.Label; // Initialization code _currentCodeSection = subprogramObject.InitializationCode; // initialize the pointer to zero AddOpcode(new OpcodePush(subprogramObject.PointerIdentifier)); AddOpcode(new OpcodePush(0)); AddOpcode(new OpcodeStore()); } } } }
private void PreProcessRunStatement(ParseNode node) { NodeStartHousekeeping(node); if (options.LoadProgramsInSameAddressSpace) { int progNameIndex = 1; if (node.Nodes[1].Token.Type == TokenType.ONCE) { ++progNameIndex; } bool hasOn = node.Nodes.Any(cn => cn.Token.Type == TokenType.ON); if (!hasOn) { string subprogramName = node.Nodes[progNameIndex].Token.Text; // It assumes it already knows at compile-time how many unique program filenames exist, if (!context.Subprograms.Contains(subprogramName)) // which it uses to decide how many of these blocks to make, { // which is why we can't defer run filenames until runtime like we can with the others. Subprogram subprogramObject = context.Subprograms.GetSubprogram(subprogramName); // Function code currentCodeSection = subprogramObject.FunctionCode; // verify if the program has been loaded Opcode functionStart = AddOpcode(new OpcodePush(subprogramObject.PointerIdentifier)); // Becuse of Cpu.SaveAndClearPointers(), the subprogram's pointer identifier won't // exist in this program context until it has been compiled once. If it does exist, // then skip the compiling step and just run the code that's already there: AddOpcode(new OpcodeExists()); // branch to where the compiler loads the code: OpcodeBranchIfFalse branchToLoad = new OpcodeBranchIfFalse(); AddOpcode(branchToLoad); // Now the top of the stack should be the argument pushed by // VisitRunStatement that flags if there was a 'once' keyword: // If there was no 'once', then branch to where the already // loaded code gets run, even though it's been run before: OpcodeBranchIfFalse branchToRun = new OpcodeBranchIfFalse(); AddOpcode(branchToRun); // If it falls through to here, that means the code was already // loaded, AND the 'once' keyword was present in the instance // where this loading function got called, so just do nothing // and return. AddOpcode(new OpcodePush(0)); AddOpcode(new OpcodeReturn(0)); // if it wasn't then load it now: // First throw away the 'once' argument since it doesn't matter when // we're going to be compiling regardless: OpcodePop firstOpcodeOfLoadSection = new OpcodePop(); AddOpcode(firstOpcodeOfLoadSection); branchToLoad.DestinationLabel = firstOpcodeOfLoadSection.Label; AddOpcode(new OpcodePush(subprogramObject.PointerIdentifier)); AddOpcode(new OpcodePush(new KOSArgMarkerType())); AddOpcode(new OpcodePush(subprogramObject.SubprogramName)); AddOpcode(new OpcodePush(null)); // The output filename - only used for compile-to-file rather than for running. AddOpcode(new OpcodeCall("load()")); AddOpcode(new OpcodePop()); // all functions now return a value even if it's a dummy we ignore. // store the address of the program in the pointer variable // (the load() function pushes the address onto the stack) AddOpcode(new OpcodeStore()); // call the program Opcode callOpcode = AddOpcode(new OpcodeCall(subprogramObject.PointerIdentifier)); // set the call opcode as the destination of the previous branch branchToRun.DestinationLabel = callOpcode.Label; // return to the caller address, after adding a dummy return val: // maybe TODO? Right now the RETURN command is being prevented from being used outside // a function declaration. But in principle we could have programs return exit codes // using the same architecture, and in fact that is why this dummy return value is needed, // because OpcodeReturn now expects such a return value to exist and throws an exception when it // does not. // If an EXIT command was implemented, it would maybe allow an exit code that can be read here: AddOpcode(new OpcodePop()); // for now: throw away return code from subprogram. AddOpcode(new OpcodePush(0)); // Replace it with new dummy return code. AddOpcode(new OpcodeReturn(0)); // return that. // set the function start label subprogramObject.FunctionLabel = functionStart.Label; // Initialization code currentCodeSection = subprogramObject.InitializationCode; // removed pointer initialization since it overwrote any existing values when loading ksm files. } } } }
private void PreProcessRunStatement(ParseNode node) { NodeStartHousekeeping(node); if (options.LoadProgramsInSameAddressSpace) { bool hasOn = node.Nodes.Any(cn => cn.Token.Type == TokenType.ON); if (!hasOn) { string subprogramName = node.Nodes[1].Token.Text; // It assumes it already knows at compile-time how many unique program filenames exist, if (!context.Subprograms.Contains(subprogramName)) // which it uses to decide how many of these blocks to make, { // which is why we can't defer run filenames until runtime like we can with the others. Subprogram subprogramObject = context.Subprograms.GetSubprogram(subprogramName); // Function code currentCodeSection = subprogramObject.FunctionCode; // verify if the program has been loaded Opcode functionStart = AddOpcode(new OpcodePush(subprogramObject.PointerIdentifier)); AddOpcode(new OpcodePush(-1)); AddOpcode(new OpcodeCompareEqual()); OpcodeBranchIfFalse branchOpcode = new OpcodeBranchIfFalse(); AddOpcode(branchOpcode); // if it wasn't then load it now AddOpcode(new OpcodePush(subprogramObject.PointerIdentifier)); AddOpcode(new OpcodePush(OpcodeCall.ARG_MARKER_STRING)); AddOpcode(new OpcodePush(subprogramObject.SubprogramName)); AddOpcode(new OpcodePush(null)); // The output filename - only used for compile-to-file rather than for running. AddOpcode(new OpcodeCall("load()")); AddOpcode(new OpcodePop()); // all functions now return a value even if it's a dummy we ignore. // store the address of the program in the pointer variable // (the load() function pushes the address onto the stack) AddOpcode(new OpcodeStore()); // call the program Opcode callOpcode = AddOpcode(new OpcodeCall(subprogramObject.PointerIdentifier)); // set the call opcode as the destination of the previous branch branchOpcode.DestinationLabel = callOpcode.Label; // return to the caller address, after adding a dummy return val: // maybe TODO? Right now the RETURN command is being prevented from being used outside // a function declaration. But in principle we could have programs return exit codes // using the same architecture, and in fact that is why this dummy return value is needed, // because OpcodeReturn now expects such a return value to exist and throws an exception when it // does not. // If an EXIT command was implemented, it would maybe allow an exit code that can be read here: AddOpcode(new OpcodePop()); // for now: throw away return code from subprogram. AddOpcode(new OpcodePush(0)); // Replace it with new dummy return code. AddOpcode(new OpcodeReturn(0)); // return that. // set the function start label subprogramObject.FunctionLabel = functionStart.Label; // Initialization code currentCodeSection = subprogramObject.InitializationCode; // initialize the pointer to zero AddOpcode(new OpcodePush(subprogramObject.PointerIdentifier)); AddOpcode(new OpcodePush(-1)); AddOpcode(new OpcodeStore()); } } } }
protected IEnumerable <Opcode> BuildBoilerplateLoader() { List <Opcode> boilerplate = new List <Opcode>(); InternalPath path = new BuiltInPath(); // First label of the load/runner will be called "@LR00", which is important because that's // what we hardcode all the compiler-built VisitRunStatement's to call out to: labelCounter = 0; labelFormat = "@LR{0:D2}"; boilerplate.Add(new OpcodePushScope(-999, 0) { Label = nextLabel, SourcePath = path }); // High level kerboscript function calls flip the argument orders for us, but // low level kRISC does not so the parameters have to be read in stack order: // store parameter 2 in a local name: boilerplate.Add(new OpcodeStoreLocal("$runonce") { Label = nextLabel, SourcePath = path }); // store parameter 1 in a local name: boilerplate.Add(new OpcodeStoreLocal("$filename") { Label = nextLabel, SourcePath = path }); // Unconditionally call load() no matter what. load() will abort and return // early if the program was already compiled, and tell us that on the stack: boilerplate.Add(new OpcodePush(new kOS.Safe.Execution.KOSArgMarkerType()) { Label = nextLabel, SourcePath = path }); boilerplate.Add(new OpcodePush("$filename") { Label = nextLabel, SourcePath = path }); boilerplate.Add(new OpcodeEval() { Label = nextLabel, SourcePath = path }); boilerplate.Add(new OpcodePush(true) { Label = nextLabel, SourcePath = path }); // the flag that tells load() to abort early if it's already loaded: boilerplate.Add(new OpcodePush(null) { Label = nextLabel, SourcePath = path }); boilerplate.Add(new OpcodeCall("load()") { Label = nextLabel, SourcePath = path }); // Stack now has the 2 return values of load(): // Topmost value is a boolean flag for whether or not the program was already loaded. // Second-from-top value is the entry point into the program. // If load() didn't claim the program was already loaded, or if we aren't operating // in "once" mode, then jump to the part where we call the loaded program, else // fall through to a dummy do-nothing return for the "run once, but it already ran" case: Opcode branchFromOne = new OpcodeBranchIfFalse() { Label = nextLabel, SourcePath = path }; boilerplate.Add(branchFromOne); boilerplate.Add(new OpcodePush("$runonce") { Label = nextLabel, SourcePath = path }); Opcode branchFromTwo = new OpcodeBranchIfFalse() { Label = nextLabel, SourcePath = path }; boilerplate.Add(branchFromTwo); // If we fall through to this opcode (the br.false above doesn't jump), we are // in the "program already ran, so skip running it" clause of a RUN ONCE. // In that case the stack will still have the jump address returned by load(), // and also all the args that were meant to be passed to the program (which is not // getting run.) In that case, we need to pop the stack of everything above the // next KOSArgMarker to emulate what a call would have done to the stack had it run. Opcode loopStart = new OpcodePop() { Label = nextLabel, SourcePath = path }; boilerplate.Add(loopStart); boilerplate.Add(new OpcodeTestArgBottom() { Label = nextLabel, SourcePath = path }); Opcode branchThatExitsLoop = new OpcodeBranchIfTrue() { Label = nextLabel, SourcePath = path }; boilerplate.Add(branchThatExitsLoop); boilerplate.Add(new OpcodeBranchJump() { Label = nextLabel, SourcePath = path, DestinationLabel = loopStart.Label }); Opcode afterLoop = new OpcodePop() { Label = nextLabel, SourcePath = path }; // Pop the KOSArgMarker that was under the args boilerplate.Add(afterLoop); branchThatExitsLoop.DestinationLabel = afterLoop.Label; boilerplate.Add(new OpcodePush(0) { Label = nextLabel, SourcePath = path }); // ---+-- The dummy do-nothing return. boilerplate.Add(new OpcodeReturn(1) { Label = nextLabel, SourcePath = path }); // ---' // Actually call the Program from its entry Point, which is now the thing left on top // of the stack from the second return value of load(): Opcode branchTo = new OpcodeStoreLocal("$entrypoint") { Label = nextLabel, SourcePath = path }; boilerplate.Add(branchTo); boilerplate.Add(new OpcodeCall("$entrypoint") { Label = nextLabel, SourcePath = path }); boilerplate.Add(new OpcodePop() { Label = nextLabel, SourcePath = path }); boilerplate.Add(new OpcodePush(0) { Label = nextLabel, SourcePath = path }); boilerplate.Add(new OpcodeReturn(1) { Label = nextLabel, SourcePath = path }); branchFromOne.DestinationLabel = branchTo.Label; branchFromTwo.DestinationLabel = branchTo.Label; return(boilerplate); }
/// <summary> /// Process a single parameter from the parameter list for a /// function or program. i.e. if encountering the statement /// "DECLARE PARAMETER AA, BB, CC is 0." , then this method needs to be /// called 3 times, once for AA, once for BB, and once for "CC is 0": /// </summary> /// <param name="whereToStore">is it local or global or lazyglobal</param> /// <param name="identifierNode">Parse node holding the identifier of the param</param> /// <param name="expressionNode">Parse node holding the expression to initialize to if /// this is a defaultable parameter. If it is not a defaultable parameter, pass null here</param> private void VisitDeclareOneParameter(StorageModifier whereToStore, ParseNode identifierNode, ParseNode expressionNode) { if (expressionNode != null) { // This tests each defaultable parameter to see if it's at arg bottom. // The test must be repeated for each parameter rather than optimizing by // falling through to all subsequent defaulter expressions for the rest of // the parameters once the first one finds arg bottom. // This is because kerboscript does not require the declare parameters to // be contiguous statements so there may be code in between them you're // not supposed to skip over. AddOpcode(new OpcodeTestArgBottom()); OpcodeBranchIfFalse branchSkippingInit = new OpcodeBranchIfFalse(); AddOpcode(branchSkippingInit); VisitNode(expressionNode); // evals init expression on the top of the stack where the arg would have been branchSkippingInit.DestinationLabel = GetNextLabel(false); } VisitNode(identifierNode); AddOpcode(new OpcodeSwap()); AddOpcode(CreateAppropriateStoreCode(whereToStore, true)); }
private void PreProcessOnStatement(ParseNode node) { NodeStartHousekeeping(node); nextBraceIsFunction = true; // triggers aren't really functions but act like it a lot. int expressionHash = ConcatenateNodes(node).GetHashCode(); string triggerIdentifier = "on-" + expressionHash.ToString(); Trigger triggerObject = context.Triggers.GetTrigger(triggerIdentifier); currentCodeSection = triggerObject.Code; // Put the old value on top of the stack for equals comparison later: AddOpcode(new OpcodePush(triggerObject.OldValueIdentifier)); AddOpcode(new OpcodeEval()); // eval the expression for the new value, and leave it on the stack twice. VisitNode(node.Nodes[1]); AddOpcode(new OpcodeEval()); AddOpcode(new OpcodeDup()); // Put one of those two copies of the new value into the old value identifier for next time: AddOpcode(new OpcodePush(triggerObject.OldValueIdentifier)); AddOpcode(new OpcodeSwap()); AddOpcode(new OpcodeStoreGlobal()); // Use the other dup'ed copy of the new value to actually do the equals // comparison with the old value that's still under it on the stack: AddOpcode(new OpcodeCompareEqual()); OpcodeBranchIfFalse branchToBody = new OpcodeBranchIfFalse(); branchToBody.Distance = 3; AddOpcode(branchToBody); AddOpcode(new OpcodePush(true)); // wasn't triggered yet, so preserve. AddOpcode(new OpcodeReturn((short)0)); // premature return because it wasn't triggered // make flag that remembers whether to remove trigger: // defaults to true = removal should happen. string triggerKeepName = "$keep-" + triggerIdentifier; PushTriggerKeepName(triggerKeepName); AddOpcode(new OpcodePush(triggerKeepName)); AddOpcode(new OpcodePush(false)); AddOpcode(new OpcodeStoreGlobal()); VisitNode(node.Nodes[2]); // PRESERVE will determine whether or not the trigger returns true (true means // re-enable the trigger upon exit.) PopTriggerKeepName(); AddOpcode(new OpcodePush(triggerKeepName)); AddOpcode(new OpcodeReturn((short)0)); nextBraceIsFunction = false; }