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. } } } }
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); }