// Pushes [function] onto [fiber]'s callstack and invokes it. Expects [numArgs] // arguments (including the receiver) to be on the top of the stack already. // [function] can be an `ObjFn` or `ObjClosure`. public void CallFunction(Obj function, int numArgs) { CallFrame frame = new CallFrame { Fn = function, StackStart = StackTop - numArgs, Ip = 0 }; Frames.Add(frame); NumFrames++; }
// Resets [fiber] back to an initial state where it is ready to invoke [fn]. private void ResetFiber(Obj fn) { Stack = new Obj[InitialStackSize]; Capacity = InitialStackSize; Frames = new List<CallFrame>(); // Push the stack frame for the function. StackTop = 0; NumFrames = 1; OpenUpvalues = null; Caller = null; Error = null; CallerIsTrying = false; CallFrame frame = new CallFrame { Fn = fn, StackStart = 0, Ip = 0 }; Frames.Add(frame); }
// Resets [fiber] back to an initial state where it is ready to invoke [fn]. private void ResetFiber(Obj fn) { Stack = new Obj[InitialStackSize]; Capacity = InitialStackSize; Frames = new List <CallFrame>(); // Push the stack frame for the function. StackTop = 0; NumFrames = 1; OpenUpvalues = null; Caller = null; Error = null; CallerIsTrying = false; CallFrame frame = new CallFrame { Fn = fn, StackStart = 0, Ip = 0 }; Frames.Add(frame); }
// The main bytecode interpreter loop. This is where the magic happens. It is // also, as you can imagine, highly performance critical. Returns `true` if the // fiber completed without error. private bool RunInterpreter() { /* Load Frame */ CallFrame frame = Fiber.Frames[Fiber.NumFrames - 1]; int ip = frame.Ip; int stackStart = frame.StackStart; Obj[] stack = Fiber.Stack; ObjFn fn = frame.Fn as ObjFn ?? ((ObjClosure)frame.Fn).Function; byte[] bytecode = fn.Bytecode; while (true) { Instruction instruction = (Instruction)bytecode[ip++]; int index; switch (instruction) { case Instruction.LOAD_LOCAL_0: case Instruction.LOAD_LOCAL_1: case Instruction.LOAD_LOCAL_2: case Instruction.LOAD_LOCAL_3: case Instruction.LOAD_LOCAL_4: case Instruction.LOAD_LOCAL_5: case Instruction.LOAD_LOCAL_6: case Instruction.LOAD_LOCAL_7: case Instruction.LOAD_LOCAL_8: { index = stackStart + instruction - Instruction.LOAD_LOCAL_0; if (Fiber.StackTop >= Fiber.Capacity) stack = Fiber.IncreaseStack(); stack[Fiber.StackTop++] = stack[index]; break; } case Instruction.LOAD_LOCAL: { index = stackStart + bytecode[ip++]; if (Fiber.StackTop >= Fiber.Capacity) stack = Fiber.IncreaseStack(); stack[Fiber.StackTop++] = stack[index]; break; } case Instruction.LOAD_FIELD_THIS: { byte field = bytecode[ip++]; ObjInstance instance = (ObjInstance)stack[stackStart]; if (Fiber.StackTop >= Fiber.Capacity) stack = Fiber.IncreaseStack(); stack[Fiber.StackTop++] = instance.Fields[field]; break; } case Instruction.POP: { Fiber.StackTop--; break; } case Instruction.DUP: { if (Fiber.StackTop >= Fiber.Capacity) stack = Fiber.IncreaseStack(); stack[Fiber.StackTop] = stack[Fiber.StackTop - 1]; Fiber.StackTop++; break; } case Instruction.NULL: { if (Fiber.StackTop >= Fiber.Capacity) stack = Fiber.IncreaseStack(); stack[Fiber.StackTop++] = Obj.Null; break; } case Instruction.FALSE: { if (Fiber.StackTop >= Fiber.Capacity) stack = Fiber.IncreaseStack(); stack[Fiber.StackTop++] = Obj.False; break; } case Instruction.TRUE: { if (Fiber.StackTop >= Fiber.Capacity) stack = Fiber.IncreaseStack(); stack[Fiber.StackTop++] = Obj.True; break; } case Instruction.CALL_0: case Instruction.CALL_1: case Instruction.CALL_2: case Instruction.CALL_3: case Instruction.CALL_4: case Instruction.CALL_5: case Instruction.CALL_6: case Instruction.CALL_7: case Instruction.CALL_8: case Instruction.CALL_9: case Instruction.CALL_10: case Instruction.CALL_11: case Instruction.CALL_12: case Instruction.CALL_13: case Instruction.CALL_14: case Instruction.CALL_15: case Instruction.CALL_16: // Handle Super calls case Instruction.SUPER_0: case Instruction.SUPER_1: case Instruction.SUPER_2: case Instruction.SUPER_3: case Instruction.SUPER_4: case Instruction.SUPER_5: case Instruction.SUPER_6: case Instruction.SUPER_7: case Instruction.SUPER_8: case Instruction.SUPER_9: case Instruction.SUPER_10: case Instruction.SUPER_11: case Instruction.SUPER_12: case Instruction.SUPER_13: case Instruction.SUPER_14: case Instruction.SUPER_15: case Instruction.SUPER_16: { int numArgs = instruction - (instruction >= Instruction.SUPER_0 ? Instruction.SUPER_0 : Instruction.CALL_0) + 1; int symbol = (bytecode[ip] << 8) + bytecode[ip + 1]; ip += 2; // The receiver is the first argument. int argStart = Fiber.StackTop - numArgs; Obj receiver = stack[argStart]; ObjClass classObj; if (instruction < Instruction.SUPER_0) { if (receiver.Type == ObjType.Obj) classObj = receiver.ClassObj; else if (receiver.Type == ObjType.Num) classObj = NumClass; else if (receiver == Obj.True || receiver == Obj.False) classObj = BoolClass; else classObj = NullClass; } else { // The superclass is stored in a constant. classObj = fn.Constants[(bytecode[ip] << 8) + bytecode[ip + 1]] as ObjClass; ip += 2; } // If the class's method table doesn't include the symbol, bail. Method method = symbol < classObj.Methods.Length ? classObj.Methods[symbol] : null; if (method == null) { /* Method not found */ frame.Ip = ip; MethodNotFound(this, classObj, symbol); if (!HandleRuntimeError()) return false; frame = Fiber.Frames[Fiber.NumFrames - 1]; ip = frame.Ip; stackStart = frame.StackStart; stack = Fiber.Stack; fn = (frame.Fn as ObjFn) ?? (frame.Fn as ObjClosure).Function; bytecode = fn.Bytecode; break; } if (method.MType == MethodType.Primitive) { // After calling this, the result will be in the first arg slot. if (method.Primitive(this, stack, argStart)) { Fiber.StackTop = argStart + 1; } else { frame.Ip = ip; if (Fiber.Error != null && Fiber.Error != Obj.Null) { if (!HandleRuntimeError()) return false; } else { // If we don't have a fiber to switch to, stop interpreting. if (stack[argStart] == Obj.Null) return true; Fiber = stack[argStart] as ObjFiber; if (Fiber == null) return false; } /* Load Frame */ frame = Fiber.Frames[Fiber.NumFrames - 1]; ip = frame.Ip; stackStart = frame.StackStart; stack = Fiber.Stack; fn = (frame.Fn as ObjFn) ?? (frame.Fn as ObjClosure).Function; bytecode = fn.Bytecode; } break; } frame.Ip = ip; if (method.MType == MethodType.Block) { receiver = method.Obj; } else if (!CheckArity(stack, numArgs, argStart)) { if (!HandleRuntimeError()) return false; frame = Fiber.Frames[Fiber.NumFrames - 1]; ip = frame.Ip; stackStart = frame.StackStart; stack = Fiber.Stack; fn = (frame.Fn as ObjFn) ?? (frame.Fn as ObjClosure).Function; bytecode = fn.Bytecode; break; } Fiber.Frames.Add(frame = new CallFrame { Fn = receiver, StackStart = argStart, Ip = 0 }); Fiber.NumFrames++; /* Load Frame */ ip = 0; stackStart = argStart; fn = (receiver as ObjFn) ?? (receiver as ObjClosure).Function; bytecode = fn.Bytecode; break; } case Instruction.STORE_LOCAL: { index = stackStart + bytecode[ip++]; stack[index] = stack[Fiber.StackTop - 1]; break; } case Instruction.CONSTANT: { if (Fiber.StackTop >= Fiber.Capacity) stack = Fiber.IncreaseStack(); stack[Fiber.StackTop++] = fn.Constants[(bytecode[ip] << 8) + bytecode[ip + 1]]; ip += 2; break; } case Instruction.LOAD_UPVALUE: { if (Fiber.StackTop >= Fiber.Capacity) stack = Fiber.IncreaseStack(); stack[Fiber.StackTop++] = ((ObjClosure)frame.Fn).Upvalues[bytecode[ip++]].Container; break; } case Instruction.STORE_UPVALUE: { ObjUpvalue[] upvalues = ((ObjClosure)frame.Fn).Upvalues; upvalues[bytecode[ip++]].Container = stack[Fiber.StackTop - 1]; break; } case Instruction.LOAD_MODULE_VAR: { if (Fiber.StackTop >= Fiber.Capacity) stack = Fiber.IncreaseStack(); stack[Fiber.StackTop++] = fn.Module.Variables[(bytecode[ip] << 8) + bytecode[ip + 1]].Container; ip += 2; break; } case Instruction.STORE_MODULE_VAR: { fn.Module.Variables[(bytecode[ip] << 8) + bytecode[ip + 1]].Container = stack[Fiber.StackTop - 1]; ip += 2; break; } case Instruction.STORE_FIELD_THIS: { byte field = bytecode[ip++]; ObjInstance instance = (ObjInstance)stack[stackStart]; instance.Fields[field] = stack[Fiber.StackTop - 1]; break; } case Instruction.LOAD_FIELD: { byte field = bytecode[ip++]; ObjInstance instance = (ObjInstance)stack[--Fiber.StackTop]; if (Fiber.StackTop >= Fiber.Capacity) stack = Fiber.IncreaseStack(); stack[Fiber.StackTop++] = instance.Fields[field]; break; } case Instruction.STORE_FIELD: { byte field = bytecode[ip++]; ObjInstance instance = (ObjInstance)stack[--Fiber.StackTop]; instance.Fields[field] = stack[Fiber.StackTop - 1]; break; } case Instruction.JUMP: { int offset = (bytecode[ip] << 8) + bytecode[ip + 1]; ip += offset + 2; break; } case Instruction.LOOP: { // Jump back to the top of the loop. int offset = (bytecode[ip] << 8) + bytecode[ip + 1]; ip += 2; ip -= offset; break; } case Instruction.JUMP_IF: { int offset = (bytecode[ip] << 8) + bytecode[ip + 1]; ip += 2; Obj condition = stack[--Fiber.StackTop]; if (condition == Obj.False || condition == Obj.Null) ip += offset; break; } case Instruction.AND: case Instruction.OR: { int offset = (bytecode[ip] << 8) + bytecode[ip + 1]; ip += 2; Obj condition = stack[Fiber.StackTop - 1]; if ((condition == Obj.Null || condition == Obj.False) ^ instruction == Instruction.OR) ip += offset; else Fiber.StackTop--; break; } case Instruction.CLOSE_UPVALUE: { Fiber.CloseUpvalue(); Fiber.StackTop--; break; } case Instruction.RETURN: { Fiber.Frames.RemoveAt(--Fiber.NumFrames); Obj result = stack[--Fiber.StackTop]; // Close any upvalues still in scope. if (Fiber.StackTop > stackStart) { while (Fiber.OpenUpvalues != null && Fiber.OpenUpvalues.Index >= stackStart) { Fiber.CloseUpvalue(); } } // If the fiber is complete, end it. if (Fiber.NumFrames == 0) { // If this is the main fiber, we're done. if (Fiber.Caller == null) return true; // We have a calling fiber to resume. Fiber = Fiber.Caller; stack = Fiber.Stack; // Store the result in the resuming fiber. stack[Fiber.StackTop - 1] = result; } else { // Discard the stack slots for the call frame (leaving one slot for the result). Fiber.StackTop = stackStart + 1; // Store the result of the block in the first slot, which is where the // caller expects it. stack[stackStart] = result; } /* Load Frame */ frame = Fiber.Frames[Fiber.NumFrames - 1]; ip = frame.Ip; stackStart = frame.StackStart; fn = frame.Fn as ObjFn ?? (frame.Fn as ObjClosure).Function; bytecode = fn.Bytecode; break; } case Instruction.CLOSURE: { ObjFn prototype = fn.Constants[(bytecode[ip] << 8) + bytecode[ip + 1]] as ObjFn; ip += 2; // Create the closure and push it on the stack before creating upvalues // so that it doesn't get collected. ObjClosure closure = new ObjClosure(prototype); if (Fiber.StackTop >= Fiber.Capacity) stack = Fiber.IncreaseStack(); stack[Fiber.StackTop++] = closure; // Capture upvalues. for (int i = 0; i < prototype.NumUpvalues; i++) { byte isLocal = bytecode[ip++]; index = bytecode[ip++]; if (isLocal > 0) { // Make an new upvalue to close over the parent's local variable. closure.Upvalues[i] = Fiber.CaptureUpvalue(stackStart + index); } else { // Use the same upvalue as the current call frame. closure.Upvalues[i] = ((ObjClosure)frame.Fn).Upvalues[index]; } } break; } case Instruction.CLASS: { Obj name = stack[Fiber.StackTop - 2]; ObjClass superclass = stack[Fiber.StackTop - 1] as ObjClass; Obj error = ValidateSuperclass(name, stack[Fiber.StackTop - 1]); if (error != null) { Fiber.Error = error; frame.Ip = ip; if (!HandleRuntimeError()) return false; /* Load Frame */ frame = Fiber.Frames[Fiber.NumFrames - 1]; ip = frame.Ip; stackStart = frame.StackStart; stack = Fiber.Stack; fn = (frame.Fn as ObjFn) ?? (frame.Fn as ObjClosure).Function; bytecode = fn.Bytecode; break; } int numFields = bytecode[ip++]; Obj classObj = new ObjClass(superclass, numFields, name as ObjString); // Don't pop the superclass and name off the stack until the subclass is // done being created, to make sure it doesn't get collected. Fiber.StackTop -= 2; // Now that we know the total number of fields, make sure we don't overflow. if (superclass.NumFields + numFields <= Compiler.MaxFields) { stack[Fiber.StackTop++] = classObj; break; } // Overflow handling frame.Ip = ip; Fiber.Error = Obj.MakeString(string.Format("Class '{0}' may not have more than 255 fields, including inherited ones.", name)); if (!HandleRuntimeError()) return false; /* Load Frame */ frame = Fiber.Frames[Fiber.NumFrames - 1]; ip = frame.Ip; stackStart = frame.StackStart; stack = Fiber.Stack; fn = (frame.Fn as ObjFn) ?? (frame.Fn as ObjClosure).Function; bytecode = fn.Bytecode; break; } case Instruction.METHOD_INSTANCE: case Instruction.METHOD_STATIC: { int symbol = (bytecode[ip] << 8) + bytecode[ip + 1]; ip += 2; ObjClass classObj = stack[Fiber.StackTop - 1] as ObjClass; Obj method = stack[Fiber.StackTop - 2]; bool isStatic = instruction == Instruction.METHOD_STATIC; if (!BindMethod(isStatic, symbol, classObj, method)) { frame.Ip = ip; Fiber.Error = Obj.MakeString("Error while binding method"); if (!HandleRuntimeError()) return false; /* Load Frame */ frame = Fiber.Frames[Fiber.NumFrames - 1]; ip = frame.Ip; stackStart = frame.StackStart; stack = Fiber.Stack; fn = (frame.Fn as ObjFn) ?? (frame.Fn as ObjClosure).Function; bytecode = fn.Bytecode; break; } Fiber.StackTop -= 2; break; } case Instruction.LOAD_MODULE: { Obj name = fn.Constants[(bytecode[ip] << 8) + bytecode[ip + 1]]; ip += 2; Obj result = ImportModule(name); // If it returned a string, it was an error message. if ((result is ObjString)) { frame.Ip = ip; Fiber.Error = result; if (!HandleRuntimeError()) return false; /* Load Frame */ frame = Fiber.Frames[Fiber.NumFrames - 1]; ip = frame.Ip; stackStart = frame.StackStart; stack = Fiber.Stack; fn = (frame.Fn as ObjFn) ?? (frame.Fn as ObjClosure).Function; bytecode = fn.Bytecode; break; } // Make a slot that the module's fiber can use to store its result in. // It ends up getting discarded, but CODE_RETURN expects to be able to // place a value there. if (Fiber.StackTop >= Fiber.Capacity) stack = Fiber.IncreaseStack(); stack[Fiber.StackTop++] = Obj.Null; // If it returned a fiber to execute the module body, switch to it. if (result is ObjFiber) { // Return to this module when that one is done. (result as ObjFiber).Caller = Fiber; frame.Ip = ip; Fiber = (result as ObjFiber); /* Load Frame */ frame = Fiber.Frames[Fiber.NumFrames - 1]; ip = frame.Ip; stackStart = frame.StackStart; stack = Fiber.Stack; fn = (frame.Fn as ObjFn) ?? (frame.Fn as ObjClosure).Function; bytecode = fn.Bytecode; } break; } case Instruction.IMPORT_VARIABLE: { Obj module = fn.Constants[(bytecode[ip] << 8) + bytecode[ip + 1]]; ip += 2; Obj variable = fn.Constants[(bytecode[ip] << 8) + bytecode[ip + 1]]; ip += 2; Obj result; if (ImportVariable(module, variable, out result)) { if (Fiber.StackTop >= Fiber.Capacity) stack = Fiber.IncreaseStack(); stack[Fiber.StackTop++] = result; } else { frame.Ip = ip; Fiber.Error = result; if (!HandleRuntimeError()) return false; /* Load Frame */ frame = Fiber.Frames[Fiber.NumFrames - 1]; ip = frame.Ip; stackStart = frame.StackStart; stack = Fiber.Stack; fn = (frame.Fn as ObjFn) ?? (frame.Fn as ObjClosure).Function; bytecode = fn.Bytecode; } break; } case Instruction.CONSTRUCT: { int stackPosition = Fiber.StackTop - 1 + (Instruction.CALL_0 - (Instruction)bytecode[ip]); ObjClass v = stack[stackPosition] as ObjClass; if (v == null) { Fiber.Error = Obj.MakeString("'this' should be a class."); if (!HandleRuntimeError()) return false; /* Load Frame */ frame = Fiber.Frames[Fiber.NumFrames - 1]; ip = frame.Ip; stackStart = frame.StackStart; stack = Fiber.Stack; fn = (frame.Fn as ObjFn) ?? (frame.Fn as ObjClosure).Function; bytecode = fn.Bytecode; break; } stack[stackPosition] = new ObjInstance(v); } break; case Instruction.FOREIGN_CLASS: // Not yet implemented break; case Instruction.FOREIGN_CONSTRUCT: // Not yet implemented break; case Instruction.END: // A CODE_END should always be preceded by a CODE_RETURN. If we get here, // the compiler generated wrong code. return false; } } // We should only exit this function from an explicit return from CODE_RETURN // or a runtime error. }
// The main bytecode interpreter loop. This is where the magic happens. It is // also, as you can imagine, highly performance critical. Returns `true` if the // fiber completed without error. private bool RunInterpreter() { Instruction instruction; int index; /* Load Frame */ CallFrame frame = fiber.Frames[fiber.NumFrames - 1]; int ip = frame.ip; int stackStart = frame.StackStart; Value[] stack = fiber.Stack; Value[] args = new Value[17]; ObjFn fn = frame.fn as ObjFn ?? ((ObjClosure)frame.fn).Function; byte[] bytecode = fn.Bytecode; while (true) { switch (instruction = (Instruction)bytecode[ip++]) { case Instruction.LOAD_LOCAL_0: case Instruction.LOAD_LOCAL_1: case Instruction.LOAD_LOCAL_2: case Instruction.LOAD_LOCAL_3: case Instruction.LOAD_LOCAL_4: case Instruction.LOAD_LOCAL_5: case Instruction.LOAD_LOCAL_6: case Instruction.LOAD_LOCAL_7: case Instruction.LOAD_LOCAL_8: index = stackStart + (int)instruction; // LOAD_LOCAL_0 has code 0 if (fiber.StackTop >= fiber.Capacity) stack = fiber.IncreaseStack(); stack[fiber.StackTop++] = stack[index]; break; case Instruction.LOAD_LOCAL: index = stackStart + bytecode[ip++]; if (fiber.StackTop >= fiber.Capacity) stack = fiber.IncreaseStack(); stack[fiber.StackTop++] = stack[index]; break; case Instruction.LOAD_FIELD_THIS: { byte field = bytecode[ip++]; Value receiver = stack[stackStart]; ObjInstance instance = receiver.Obj as ObjInstance; if (fiber.StackTop >= fiber.Capacity) fiber.IncreaseStack(); stack[fiber.StackTop++] = instance.Fields[field]; break; } case Instruction.POP: fiber.StackTop--; break; case Instruction.DUP: if (fiber.StackTop >= fiber.Capacity) stack = fiber.IncreaseStack(); stack[fiber.StackTop] = stack[fiber.StackTop - 1]; fiber.StackTop++; break; case Instruction.NULL: if (fiber.StackTop >= fiber.Capacity) stack = fiber.IncreaseStack(); stack[fiber.StackTop++] = new Value(ValueType.Null); break; case Instruction.FALSE: if (fiber.StackTop >= fiber.Capacity) stack = fiber.IncreaseStack(); stack[fiber.StackTop++] = new Value(ValueType.False); break; case Instruction.TRUE: if (fiber.StackTop >= fiber.Capacity) stack = fiber.IncreaseStack(); stack[fiber.StackTop++] = new Value(ValueType.True); break; case Instruction.CALL_0: case Instruction.CALL_1: case Instruction.CALL_2: case Instruction.CALL_3: case Instruction.CALL_4: case Instruction.CALL_5: case Instruction.CALL_6: case Instruction.CALL_7: case Instruction.CALL_8: case Instruction.CALL_9: case Instruction.CALL_10: case Instruction.CALL_11: case Instruction.CALL_12: case Instruction.CALL_13: case Instruction.CALL_14: case Instruction.CALL_15: case Instruction.CALL_16: // Handle Super calls case Instruction.SUPER_0: case Instruction.SUPER_1: case Instruction.SUPER_2: case Instruction.SUPER_3: case Instruction.SUPER_4: case Instruction.SUPER_5: case Instruction.SUPER_6: case Instruction.SUPER_7: case Instruction.SUPER_8: case Instruction.SUPER_9: case Instruction.SUPER_10: case Instruction.SUPER_11: case Instruction.SUPER_12: case Instruction.SUPER_13: case Instruction.SUPER_14: case Instruction.SUPER_15: case Instruction.SUPER_16: { int numArgs = (int)instruction & 63; int symbol = (bytecode[ip] << 8) + bytecode[ip + 1]; ip += 2; // The receiver is the first argument. int argStart = fiber.StackTop - numArgs; Value receiver = stack[argStart]; ObjClass classObj; if (instruction < Instruction.SUPER_0) { if (receiver.Type == ValueType.Obj) { classObj = receiver.Obj.ClassObj; } else if (receiver.Type == ValueType.Num) { classObj = NumClass; } else if (receiver.Type == ValueType.True || receiver.Type == ValueType.False) { classObj = BoolClass; } else { classObj = NullClass; } } else { // The superclass is stored in a constant. classObj = fn.Constants[(bytecode[ip] << 8) + bytecode[ip + 1]].Obj as ObjClass; ip += 2; } Method[] methods = classObj.Methods; // If the class's method table doesn't include the symbol, bail. if (symbol < methods.Length) { Method method = methods[symbol]; if (method != null) { if (method.mType == MethodType.Primitive) { for (int i = 0; i < numArgs; i++) args[i] = stack[argStart + i]; // After calling this, the result will be in the first arg slot. PrimitiveResult result = method.primitive(this, fiber, args); if (result == PrimitiveResult.Value) { fiber.StackTop = argStart + 1; stack[argStart] = args[0]; Instruction next = (Instruction)bytecode[ip]; if (next == Instruction.STORE_LOCAL) { index = stackStart + bytecode[ip + 1]; stack[index] = args[0]; ip += 2; } break; } frame.ip = ip; switch (result) { case PrimitiveResult.RunFiber: // If we don't have a fiber to switch to, stop interpreting. if (args[0].Type == ValueType.Null) return true; fiber = args[0].Obj as ObjFiber; /* Load Frame */ frame = fiber.Frames[fiber.NumFrames - 1]; ip = frame.ip; stackStart = frame.StackStart; stack = fiber.Stack; fn = (frame.fn as ObjFn) ?? (frame.fn as ObjClosure).Function; bytecode = fn.Bytecode; break; case PrimitiveResult.Call: fiber.Frames.Add(frame = new CallFrame { fn = receiver.Obj, StackStart = argStart, ip = 0 }); fiber.NumFrames++; /* Load Frame */ ip = 0; stackStart = argStart; fn = (frame.fn as ObjFn) ?? (frame.fn as ObjClosure).Function; bytecode = fn.Bytecode; break; case PrimitiveResult.Error: RUNTIME_ERROR(fiber, args[0]); if (fiber == null) return false; frame = fiber.Frames[fiber.NumFrames - 1]; ip = frame.ip; stackStart = frame.StackStart; stack = fiber.Stack; fn = (frame.fn as ObjFn) ?? (frame.fn as ObjClosure).Function; bytecode = fn.Bytecode; break; } break; } if (method.mType == MethodType.Block) { Obj mObj = method.obj; frame.ip = ip; fiber.Frames.Add(frame = new CallFrame { fn = mObj, StackStart = argStart, ip = 0 }); fiber.NumFrames++; /* Load Frame */ ip = 0; stackStart = argStart; fn = (mObj as ObjFn) ?? (mObj as ObjClosure).Function; bytecode = fn.Bytecode; break; } } } /* Method not found */ frame.ip = ip; RUNTIME_ERROR(fiber, MethodNotFound(this, classObj, symbol)); if (fiber == null) return false; frame = fiber.Frames[fiber.NumFrames - 1]; ip = frame.ip; stackStart = frame.StackStart; stack = fiber.Stack; fn = (frame.fn as ObjFn) ?? (frame.fn as ObjClosure).Function; bytecode = fn.Bytecode; } break; case Instruction.STORE_LOCAL: index = stackStart + bytecode[ip++]; stack[index] = stack[fiber.StackTop - 1]; break; case Instruction.CONSTANT: if (fiber.StackTop >= fiber.Capacity) stack = fiber.IncreaseStack(); stack[fiber.StackTop++] = fn.Constants[(bytecode[ip] << 8) + bytecode[ip + 1]]; ip += 2; break; case Instruction.LOAD_UPVALUE: { if (fiber.StackTop >= fiber.Capacity) stack = fiber.IncreaseStack(); stack[fiber.StackTop++] = ((ObjClosure)frame.fn).Upvalues[bytecode[ip++]].Container; break; } case Instruction.STORE_UPVALUE: { ObjUpvalue[] upvalues = ((ObjClosure)frame.fn).Upvalues; upvalues[bytecode[ip++]].Container = stack[fiber.StackTop - 1]; break; } case Instruction.LOAD_MODULE_VAR: if (fiber.StackTop >= fiber.Capacity) stack = fiber.IncreaseStack(); stack[fiber.StackTop++] = fn.Module.Variables[(bytecode[ip] << 8) + bytecode[ip + 1]].Container; ip += 2; break; case Instruction.STORE_MODULE_VAR: fn.Module.Variables[(bytecode[ip] << 8) + bytecode[ip + 1]].Container = stack[fiber.StackTop - 1]; ip += 2; break; case Instruction.STORE_FIELD_THIS: { byte field = bytecode[ip++]; Value receiver = stack[stackStart]; ObjInstance instance = receiver.Obj as ObjInstance; instance.Fields[field] = stack[fiber.StackTop - 1]; break; } case Instruction.LOAD_FIELD: { byte field = bytecode[ip++]; Value receiver = stack[--fiber.StackTop]; ObjInstance instance = receiver.Obj as ObjInstance; if (fiber.StackTop >= fiber.Capacity) stack = fiber.IncreaseStack(); stack[fiber.StackTop++] = instance.Fields[field]; break; } case Instruction.STORE_FIELD: { byte field = bytecode[ip++]; Value receiver = stack[--fiber.StackTop]; ObjInstance instance = receiver.Obj as ObjInstance; instance.Fields[field] = stack[fiber.StackTop - 1]; break; } case Instruction.JUMP: { int offset = (bytecode[ip] << 8) + bytecode[ip + 1]; ip += offset + 2; break; } case Instruction.LOOP: { // Jump back to the top of the loop. int offset = (bytecode[ip] << 8) + bytecode[ip + 1]; ip += 2; ip -= offset; break; } case Instruction.JUMP_IF: { int offset = (bytecode[ip] << 8) + bytecode[ip + 1]; ip += 2; ValueType condition = stack[--fiber.StackTop].Type; if (condition == ValueType.False || condition == ValueType.Null) ip += offset; break; } case Instruction.AND: { int offset = (bytecode[ip] << 8) + bytecode[ip + 1]; ip += 2; ValueType condition = stack[fiber.StackTop - 1].Type; switch (condition) { case ValueType.Null: case ValueType.False: ip += offset; break; default: fiber.StackTop--; break; } break; } case Instruction.OR: { int offset = (bytecode[ip] << 8) + bytecode[ip + 1]; ip += 2; Value condition = stack[fiber.StackTop - 1]; switch (condition.Type) { case ValueType.Null: case ValueType.False: fiber.StackTop--; break; default: ip += offset; break; } break; } case Instruction.CLOSE_UPVALUE: fiber.CloseUpvalue(); fiber.StackTop--; break; case Instruction.RETURN: { fiber.Frames.RemoveAt(--fiber.NumFrames); Value result = stack[--fiber.StackTop]; // Close any upvalues still in scope. if (fiber.StackTop > stackStart) { Value first = stack[stackStart]; while (fiber.OpenUpvalues != null && fiber.OpenUpvalues.Container != first) { fiber.CloseUpvalue(); } fiber.CloseUpvalue(); } // If the fiber is complete, end it. if (fiber.NumFrames == 0) { // If this is the main fiber, we're done. if (fiber.Caller == null) return true; // We have a calling fiber to resume. fiber = fiber.Caller; stack = fiber.Stack; // Store the result in the resuming fiber. stack[fiber.StackTop - 1] = result; } else { // Discard the stack slots for the call frame (leaving one slot for the result). fiber.StackTop = stackStart + 1; // Store the result of the block in the first slot, which is where the // caller expects it. stack[fiber.StackTop - 1] = result; } /* Load Frame */ frame = fiber.Frames[fiber.NumFrames - 1]; ip = frame.ip; stackStart = frame.StackStart; fn = frame.fn as ObjFn ?? (frame.fn as ObjClosure).Function; bytecode = fn.Bytecode; break; } case Instruction.CLOSURE: { ObjFn prototype = fn.Constants[(bytecode[ip] << 8) + bytecode[ip + 1]].Obj as ObjFn; ip += 2; // Create the closure and push it on the stack before creating upvalues // so that it doesn't get collected. ObjClosure closure = new ObjClosure(prototype); if (fiber.StackTop >= fiber.Capacity) stack = fiber.IncreaseStack(); stack[fiber.StackTop++] = new Value(closure); // Capture upvalues. for (int i = 0; i < prototype.NumUpvalues; i++) { byte isLocal = bytecode[ip++]; index = bytecode[ip++]; if (isLocal > 0) { // Make an new upvalue to close over the parent's local variable. closure.Upvalues[i] = fiber.CaptureUpvalue(stack[stackStart + index]); } else { // Use the same upvalue as the current call frame. closure.Upvalues[i] = ((ObjClosure)frame.fn).Upvalues[index]; } } break; } case Instruction.CLASS: { Value name = stack[fiber.StackTop - 2]; ObjClass superclass = ObjectClass; // Use implicit Object superclass if none given. if (stack[fiber.StackTop - 1].Type != ValueType.Null) { Value error = ValidateSuperclass(name, stack[fiber.StackTop - 1]); if (error != null) { frame.ip = ip; RUNTIME_ERROR(fiber, error); if (fiber == null) return false; /* Load Frame */ frame = fiber.Frames[fiber.NumFrames - 1]; ip = frame.ip; stackStart = frame.StackStart; stack = fiber.Stack; fn = (frame.fn as ObjFn) ?? (frame.fn as ObjClosure).Function; bytecode = fn.Bytecode; break; } superclass = stack[fiber.StackTop - 1].Obj as ObjClass; } int numFields = bytecode[ip++]; Value classObj = new Value(new ObjClass(superclass, numFields, name.Obj as ObjString)); // Don't pop the superclass and name off the stack until the subclass is // done being created, to make sure it doesn't get collected. fiber.StackTop -= 2; // Now that we know the total number of fields, make sure we don't overflow. if (superclass.NumFields + numFields > Compiler.MAX_FIELDS) { frame.ip = ip; RUNTIME_ERROR(fiber, new Value(string.Format("Class '{0}' may not have more than 255 fields, including inherited ones.", name.Obj))); if (fiber == null) return false; /* Load Frame */ frame = fiber.Frames[fiber.NumFrames - 1]; ip = frame.ip; stackStart = frame.StackStart; stack = fiber.Stack; fn = (frame.fn as ObjFn) ?? (frame.fn as ObjClosure).Function; bytecode = fn.Bytecode; break; } if (fiber.StackTop >= fiber.Capacity) stack = fiber.IncreaseStack(); stack[fiber.StackTop++] = classObj; break; } case Instruction.METHOD_INSTANCE: case Instruction.METHOD_STATIC: { int symbol = (bytecode[ip] << 8) + bytecode[ip + 1]; ip += 2; ObjClass classObj = stack[fiber.StackTop - 1].Obj as ObjClass; Value method = stack[fiber.StackTop - 2]; MethodType methodType = instruction == Instruction.METHOD_INSTANCE ? MethodType.None : MethodType.Static; Value error = BindMethod(methodType, symbol, classObj, method); if ((error.Obj is ObjString)) { frame.ip = ip; RUNTIME_ERROR(fiber, error); if (fiber == null) return false; /* Load Frame */ frame = fiber.Frames[fiber.NumFrames - 1]; ip = frame.ip; stackStart = frame.StackStart; stack = fiber.Stack; fn = (frame.fn as ObjFn) ?? (frame.fn as ObjClosure).Function; bytecode = fn.Bytecode; break; } fiber.StackTop -= 2; break; } case Instruction.LOAD_MODULE: { Value name = fn.Constants[(bytecode[ip] << 8) + bytecode[ip + 1]]; ip += 2; Value result = ImportModule(name); // If it returned a string, it was an error message. if ((result.Obj is ObjString)) { frame.ip = ip; RUNTIME_ERROR(fiber, result); if (fiber == null) return false; /* Load Frame */ frame = fiber.Frames[fiber.NumFrames - 1]; ip = frame.ip; stackStart = frame.StackStart; stack = fiber.Stack; fn = (frame.fn as ObjFn) ?? (frame.fn as ObjClosure).Function; bytecode = fn.Bytecode; break; } // Make a slot that the module's fiber can use to store its result in. // It ends up getting discarded, but CODE_RETURN expects to be able to // place a value there. if (fiber.StackTop >= fiber.Capacity) stack = fiber.IncreaseStack(); stack[fiber.StackTop++] = Value.Null; // If it returned a fiber to execute the module body, switch to it. if (result.Obj is ObjFiber) { // Return to this module when that one is done. (result.Obj as ObjFiber).Caller = fiber; frame.ip = ip; fiber = (result.Obj as ObjFiber); /* Load Frame */ frame = fiber.Frames[fiber.NumFrames - 1]; ip = frame.ip; stackStart = frame.StackStart; stack = fiber.Stack; fn = (frame.fn as ObjFn) ?? (frame.fn as ObjClosure).Function; bytecode = fn.Bytecode; } break; } case Instruction.IMPORT_VARIABLE: { Value module = fn.Constants[(bytecode[ip] << 8) + bytecode[ip + 1]]; ip += 2; Value variable = fn.Constants[(bytecode[ip] << 8) + bytecode[ip + 1]]; ip += 2; Value result; if (ImportVariable(module, variable, out result)) { if (fiber.StackTop >= fiber.Capacity) stack = fiber.IncreaseStack(); stack[fiber.StackTop++] = result; } else { frame.ip = ip; RUNTIME_ERROR(fiber, result); if (fiber == null) return false; /* Load Frame */ frame = fiber.Frames[fiber.NumFrames - 1]; ip = frame.ip; stackStart = frame.StackStart; stack = fiber.Stack; fn = (frame.fn as ObjFn) ?? (frame.fn as ObjClosure).Function; bytecode = fn.Bytecode; } break; } case Instruction.END: // A CODE_END should always be preceded by a CODE_RETURN. If we get here, // the compiler generated wrong code. return false; } } // We should only exit this function from an explicit return from CODE_RETURN // or a runtime error. }