// Creates a new class object as well as its associated metaclass. public ObjClass(ObjClass superclass, int numFields, ObjString name) { Methods = new Method[InitialMethodSize]; Superclass = superclass; NumFields = numFields; Name = name; // Create the metaclass. ObjString metaclassName = MakeString(name + " metaclass"); ObjClass metaclass = new ObjClass(0, metaclassName) { ClassObj = ClassClass }; // Metaclasses always inherit Class and do not parallel the non-metaclass // hierarchy. metaclass.BindSuperclass(ClassClass); ClassObj = metaclass; BindSuperclass(superclass); }
public static void BindMethodCode(ObjClass classObj, ObjFn fn) { int ip = 0; for (; ; ) { Instruction instruction = (Instruction)fn.Bytecode[ip++]; switch (instruction) { case Instruction.LoadField: case Instruction.StoreField: case Instruction.LoadFieldThis: case Instruction.StoreFieldThis: // Shift this class's fields down past the inherited ones. We don't // check for overflow here because we'll see if the number of fields // overflows when the subclass is created. fn.Bytecode[ip++] += (byte)classObj.Superclass.NumFields; break; case Instruction.Super0: case Instruction.Super1: case Instruction.Super2: case Instruction.Super3: case Instruction.Super4: case Instruction.Super5: case Instruction.Super6: case Instruction.Super7: case Instruction.Super8: case Instruction.Super9: case Instruction.Super10: case Instruction.Super11: case Instruction.Super12: case Instruction.Super13: case Instruction.Super14: case Instruction.Super15: case Instruction.Super16: { // Skip over the symbol. ip += 2; // Fill in the constant slot with a reference to the superclass. int constant = (fn.Bytecode[ip] << 8) | fn.Bytecode[ip + 1]; fn.Constants[constant] = classObj.Superclass; break; } case Instruction.Closure: { // Bind the nested closure too. int constant = (fn.Bytecode[ip] << 8) | fn.Bytecode[ip + 1]; BindMethodCode(classObj, fn.Constants[constant] as ObjFn); ip += GetNumArguments(fn.Bytecode, new List<Obj>(fn.Constants), ip - 1); break; } case Instruction.End: return; default: // Other instructions are unaffected, so just skip over them. ip += GetNumArguments(fn.Bytecode, new List<Obj>(fn.Constants), ip - 1); break; } } }
public void Call(ObjClass objClass, string s) { if (!MethodNames.Contains(s)) { MethodNames.Add(s); } int symbol = MethodNames.IndexOf(s); objClass.BindMethod(symbol, new Method { MType = MethodType.Call }); }
// 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; Obj[] stack = Fiber.Stack; ObjFn fn = frame.Fn as ObjFn ?? ((ObjClosure)frame.Fn).Function; byte[] bytecode = fn.Bytecode; while (true) { switch (instruction = (Instruction)bytecode[ip++]) { case Instruction.LoadLocal0: case Instruction.LoadLocal1: case Instruction.LoadLocal2: case Instruction.LoadLocal3: case Instruction.LoadLocal4: case Instruction.LoadLocal5: case Instruction.LoadLocal6: case Instruction.LoadLocal7: case Instruction.LoadLocal8: 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.LoadLocal: index = stackStart + bytecode[ip++]; if (Fiber.StackTop >= Fiber.Capacity) stack = Fiber.IncreaseStack(); stack[Fiber.StackTop++] = stack[index]; break; case Instruction.LoadFieldThis: { byte field = bytecode[ip++]; Obj receiver = stack[stackStart]; ObjInstance instance = receiver 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++] = 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.Zero: if (Fiber.StackTop >= Fiber.Capacity) stack = Fiber.IncreaseStack(); stack[Fiber.StackTop++] = Obj.Zero; break; case Instruction.One: if (Fiber.StackTop >= Fiber.Capacity) stack = Fiber.IncreaseStack(); stack[Fiber.StackTop++] = Obj.One; break; case Instruction.Call0: case Instruction.Call1: case Instruction.Call2: case Instruction.Call3: case Instruction.Call4: case Instruction.Call5: case Instruction.Call6: case Instruction.Call7: case Instruction.Call8: case Instruction.Call9: case Instruction.Call10: case Instruction.Call11: case Instruction.Call12: case Instruction.Call13: case Instruction.Call14: case Instruction.Call15: case Instruction.Call16: // Handle Super calls case Instruction.Super0: case Instruction.Super1: case Instruction.Super2: case Instruction.Super3: case Instruction.Super4: case Instruction.Super5: case Instruction.Super6: case Instruction.Super7: case Instruction.Super8: case Instruction.Super9: case Instruction.Super10: case Instruction.Super11: case Instruction.Super12: case Instruction.Super13: case Instruction.Super14: case Instruction.Super15: case Instruction.Super16: { int numArgs = instruction - (instruction >= Instruction.Super0 ? Instruction.Super0 : Instruction.Call0) + 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.Super0) { 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.StoreLocal: index = stackStart + bytecode[ip++]; stack[index] = stack[Fiber.StackTop - 1]; break; case Instruction.SmallConstant: if (Fiber.StackTop >= Fiber.Capacity) stack = Fiber.IncreaseStack(); stack[Fiber.StackTop++] = fn.Constants[bytecode[ip]]; ip += 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.LoadUpvalue: { if (Fiber.StackTop >= Fiber.Capacity) stack = Fiber.IncreaseStack(); stack[Fiber.StackTop++] = ((ObjClosure)frame.Fn).Upvalues[bytecode[ip++]].Container; break; } case Instruction.StoreUpvalue: { ObjUpvalue[] upvalues = ((ObjClosure)frame.Fn).Upvalues; upvalues[bytecode[ip++]].Container = stack[Fiber.StackTop - 1]; break; } case Instruction.LoadModuleVar: 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.StoreModuleVar: fn.Module.Variables[(bytecode[ip] << 8) + bytecode[ip + 1]].Container = stack[Fiber.StackTop - 1]; ip += 2; break; case Instruction.StoreFieldThis: { byte field = bytecode[ip++]; Obj receiver = stack[stackStart]; ObjInstance instance = receiver as ObjInstance; instance.Fields[field] = stack[Fiber.StackTop - 1]; break; } case Instruction.LoadField: { byte field = bytecode[ip++]; Obj receiver = stack[--Fiber.StackTop]; ObjInstance instance = receiver as ObjInstance; if (Fiber.StackTop >= Fiber.Capacity) stack = Fiber.IncreaseStack(); stack[Fiber.StackTop++] = instance.Fields[field]; break; } case Instruction.StoreField: { byte field = bytecode[ip++]; Obj receiver = stack[--Fiber.StackTop]; ObjInstance instance = receiver 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.JumpIf: { 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: { int offset = (bytecode[ip] << 8) + bytecode[ip + 1]; ip += 2; ObjType condition = stack[Fiber.StackTop - 1].Type; switch (condition) { case ObjType.Null: case ObjType.False: ip += offset; break; default: Fiber.StackTop--; break; } break; } case Instruction.Or: { int offset = (bytecode[ip] << 8) + bytecode[ip + 1]; ip += 2; Obj condition = stack[Fiber.StackTop - 1]; switch (condition.Type) { case ObjType.Null: case ObjType.False: Fiber.StackTop--; break; default: ip += offset; break; } break; } case Instruction.CloseUpvalue: 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) { Obj 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]] 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 = ObjectClass; // Use implicit Object superclass if none given. if (stack[Fiber.StackTop - 1] != Obj.Null) { Fiber.Error = ValidateSuperclass(name, stack[Fiber.StackTop - 1]); if (Fiber.Error != null) { 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; } superclass = stack[Fiber.StackTop - 1] as ObjClass; } 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) { 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; } if (Fiber.StackTop >= Fiber.Capacity) stack = Fiber.IncreaseStack(); stack[Fiber.StackTop++] = classObj; break; } case Instruction.MethodInstance: case Instruction.MethodStatic: { 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.MethodInstance; Fiber.Error = BindMethod(isStatic, symbol, classObj, method); if (Fiber.Error != null) { 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; } Fiber.StackTop -= 2; break; } case Instruction.LoadModule: { 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.ImportVariable: { 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.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. }
// Creates a string containing an appropriate method not found error for a // method with [symbol] on [classObj]. static void MethodNotFound(SophieVM vm, ObjClass classObj, int symbol) { vm.Fiber.Error = Obj.MakeString(string.Format("{0} does not implement '{1}'.", classObj.Name, vm.MethodNames[symbol])); }
// Defines [methodValue] as a method on [classObj]. private static Obj BindMethod(bool isStatic, int symbol, ObjClass classObj, Obj methodContainer) { ObjFn methodFn = methodContainer as ObjFn ?? ((ObjClosure)methodContainer).Function; // Methods are always bound against the class, and not the metaclass, even // for static methods, because static methods don't have instance fields // anyway. Compiler.BindMethodCode(classObj, methodFn); Method method = new Method { MType = MethodType.Block, Obj = methodContainer }; if (isStatic) classObj = classObj.ClassObj; classObj.BindMethod(symbol, method); return null; }
/* Anotehr Dirty Hack */ public void Primitive(ObjClass objClass, string s, Primitive func) { if (!MethodNames.Contains(s)) { MethodNames.Add(s); } int symbol = MethodNames.IndexOf(s); Method m = new Method { Primitive = func, MType = MethodType.Primitive }; objClass.BindMethod(symbol, m); }
// Makes [superclass] the superclass of [subclass], and causes subclass to // inherit its methods. This should be called before any methods are defined // on subclass. public void BindSuperclass(ObjClass sc) { if (sc == null) { throw new Exception("Must have superclass."); } Superclass = sc; // Include the superclass in the total number of fields. NumFields += sc.NumFields; // Inherit methods from its superclass. Methods = new Method[sc.Methods.Length]; sc.Methods.CopyTo(Methods,0); }