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)); // 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()); OpcodeBranchIfTrue branchOpcode = new OpcodeBranchIfTrue(); AddOpcode(branchOpcode); // if it wasn't then load it now 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 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; // removed pointer initialization since it overwrote any existing values when loading ksm files. } } } }
private void PreProcessWhenStatement(ParseNode node) { NodeStartHousekeeping(node); nextBraceIsFunction = true; // triggers aren't really functions but act like it a lot. int expressionHash = ConcatenateNodes(node).GetHashCode(); string triggerIdentifier = "when-" + expressionHash.ToString(); Trigger triggerObject = context.Triggers.GetTrigger(triggerIdentifier); currentCodeSection = triggerObject.Code; VisitNode(node.Nodes[1]); OpcodeBranchIfTrue branchToBody = new OpcodeBranchIfTrue(); 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[3]); // 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; }
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); }
// This is actually used for BOTH LOCK expressions and DEFINE FUNCTIONs, as they // both end up creating, effectively, a user function. private void PreProcessUserFunctionStatement(ParseNode node) { NodeStartHousekeeping(node); // The name of the lock or function to be executed: string userFuncIdentifier; // The syntax node for the body of the lock or function: the stuff it actually executes. ParseNode bodyNode; bool isLock = IsLockStatement(node); bool isDefFunc = IsDefineFunctionStatement(node); bool needImplicitArgBottom = false; StorageModifier storageType = GetStorageModifierFor(node); ParseNode lastSubNode = node.Nodes[node.Nodes.Count-1]; if (isLock) { userFuncIdentifier = lastSubNode.Nodes[1].Token.Text; // The IDENT of: LOCK IDENT TO EXPR. bodyNode = lastSubNode.Nodes[3]; // The EXPR of: LOCK IDENT TO EXPR. needImplicitArgBottom = true; } else if (isDefFunc) { userFuncIdentifier = lastSubNode.Nodes[1].Token.Text; // The IDENT of: DEFINE FUNCTION IDENT INSTRUCTION_BLOCK. bodyNode = lastSubNode.Nodes[2]; // The INSTRUCTION_BLOCK of: DEFINE FUNCTION IDENT INSTRUCTION_BLOCK. } else return; // Should only be the case when scanning elements for anonymous functions UserFunction userFuncObject = context.UserFunctions.GetUserFunction( userFuncIdentifier, (storageType == StorageModifier.GLOBAL ? (Int16)0 : GetContainingScopeId(node)), node ); int expressionHash = ConcatenateNodes(bodyNode).GetHashCode(); needImplicitReturn = true; // Locks always need an implicit return. Functions might not if all paths have an explicit one. // Both locks and functions also get an identifier storing their // destination instruction pointer, but the means of doing so // is slightly different. Locks always need a dummy do-nothing // function to exist at first, which then can get replaced later // when the statement containing the lock definition is encountered. // Whereas, function bodies don't get overwritten like that. They // exist exactly once, and can be "forward" called from higher up in // the same scope so they get assigned when the scope is first opened. // if (isLock && !userFuncObject.IsInitialized()) { currentCodeSection = userFuncObject.InitializationCode; if (userFuncObject.IsSystemLock()) { AddOpcode(new OpcodePush(userFuncObject.ScopelessPointerIdentifier)); AddOpcode(new OpcodeExists()); var branch = new OpcodeBranchIfTrue(); branch.Distance = 4; AddOpcode(branch); AddOpcode(new OpcodePush(userFuncObject.ScopelessPointerIdentifier)); AddOpcode(new OpcodePushRelocateLater(null), userFuncObject.DefaultLabel); AddOpcode(new OpcodeStore()); } else { // initialization code - unfortunately the lock implementation presumed global namespace // and insisted on inserting an initialization block in front of the entire program to set up // the GLOBAL lock value. This assumption was thorny to remove, so for now, we'll make the init // code consist of a dummy NOP until a better solution can be found. Note this does put a NOP // into the code PER LOCK. Which is silly. It's because lockObject.IsInitialized() doesn't // know how to tell the difference between initialization code that's deliberately empty versus // initialization code being empty because the lock has never been set up properly yet. AddOpcode(new OpcodeNOP()); } // build default dummy function to be used when this is a LOCK: currentCodeSection = userFuncObject.GetUserFunctionOpcodes(0); AddOpcode(new OpcodeArgBottom()).Label = userFuncObject.DefaultLabel;; AddOpcode(new OpcodePush("$" + userFuncObject.ScopelessIdentifier)); AddOpcode(new OpcodeReturn(0)); } // lock expression's or function body's code currentCodeSection = userFuncObject.GetUserFunctionOpcodes(expressionHash); bool secondInstanceSameLock = currentCodeSection.Count > 0; if (! secondInstanceSameLock) { forcedNextLabel = userFuncObject.GetUserFunctionLabel(expressionHash); if (isLock) // locks need to behave as if they had braces even though they don't - so they get lexical scope ids for closure reasons: BeginScope(bodyNode); if (isDefFunc) nextBraceIsFunction = true; if (needImplicitArgBottom) AddOpcode(new OpcodeArgBottom()); VisitNode(bodyNode); Int16 implicitReturnScopeDepth = 0; if (isDefFunc) nextBraceIsFunction = false; if (isLock) // locks need to behave as if they had braces even though they don't - so they get lexical scope ids for closure reasons: { EndScope(bodyNode, false); implicitReturnScopeDepth = 1; } if (needImplicitReturn) { if (isDefFunc) AddOpcode(new OpcodePush(0)); // Functions must push a dummy return val when making implicit returns. Locks already leave an expr atop the stack. AddOpcode(new OpcodeReturn(implicitReturnScopeDepth)); } userFuncObject.ScopeNode = GetContainingBlockNode(node); // This limits the scope of the function to the instruction_block the DEFINE was in. userFuncObject.IsFunction = !(isLock); } }