public static int Execute(AsyncStackInfo stackInfo, LClosure closure, int argOffset, int argSize) { //C#-Lua boundary: before. var frame = stackInfo.StackFrame; StackFrameValues values = default; values.Span = stackInfo.Thread.GetFrameValues(in frame.Data); //Set up argument type info for Lua function. var sig = new StackSignatureState(argOffset, argSize); try { //For simplicity, C#-Lua call always passes arguments in same segment. InterpreterLoop(stackInfo.Thread, closure, frame, ref sig, forceOnSameSeg: true); } catch (RecoverableException) { //TODO recover throw new NotImplementedException(); } catch (OverflowException) { //TODO check whether the last frame is Lua frame and currently executing //an int instruction. If so, deoptimize the function and recover. throw; } catch { //TODO unrolled Lua frames (deallocate, clear values). throw; } //C#-Lua boundary: after. Debug.Assert(!sig.Type.IsNull); if (sig.Type.GlobalId != (ulong)WellKnownStackSignature.EmptyV) { if (sig.Type.IsUnspecialized) { sig.VLength = sig.TotalSize; } else { sig.Type.AdjustStackToUnspecialized(in values, ref sig.VLength); } sig.Type = StackSignature.EmptyV; } return(sig.VLength); }
private static void InterpreterLoop(Thread thread, LClosure closure, StackFrameRef lastFrame, ref StackSignatureState parentSig, bool forceOnSameSeg) { //TODO recover execution //TODO in recovery, onSameSeg should be set depending on whether lastFrame's Segment //equals this frame's Segment. var proto = closure.Proto; var frame = thread.AllocateNextFrame(lastFrame, parentSig.Offset, parentSig.TotalSize, proto.StackSize, forceOnSameSeg); var sig = parentSig; sig.Offset = 0; //Args are at the beginning in the new frame. StackInfo nextNativeStackInfo = default; nextNativeStackInfo.AsyncStackInfo = new(thread, frame); var enterFrame = frame; StackFrameValues values = default; enterNewLuaFrame: nextNativeStackInfo.AsyncStackInfo.StackFrame = nextNativeStackInfo.AsyncStackInfo.StackFrame.Data.Next; values.Span = thread.GetFrameValues(in frame.Data); //Adjust argument list according to the requirement of the callee. //Also remove vararg into separate stack. if (!sig.AdjustRight(in values, proto.ParameterSig)) { throw new NotImplementedException(); } sig.MoveVararg(in values, thread.VarargStack, ref frame.Data.VarargInfo); sig.Clear(); //Push closure's upvals. Debug.Assert(closure.UpvalLists.Length <= proto.LocalRegionOffset - proto.UpvalRegionOffset); for (int i = 0; i < closure.UpvalLists.Length; ++i) { values[proto.UpvalRegionOffset + i].Object = closure.UpvalLists[i]; //We could also set types in value frame, but this region should never be accessed as other types. //This is also to be consistent for optimized functions that compresses the stack. } var pc = 0; var inst = proto.Instructions; uint ii; object nonLuaFunc; continueOldFrame: while (true) { ii = inst[pc++]; switch ((OpCodes)(ii >> 24)) { case OpCodes.NOP: break; case OpCodes.ISLT: { int a = (int)((ii >> 16) & 0xFF); int b = (int)((ii >> 8) & 0xFF); if (CompareValue(values[a], values[b]) < 0) { pc += (sbyte)(byte)(ii & 0xFF); } break; } case OpCodes.ISLE: { int a = (int)((ii >> 16) & 0xFF); int b = (int)((ii >> 8) & 0xFF); if (CompareValue(values[a], values[b]) <= 0) { pc += (sbyte)(byte)(ii & 0xFF); } break; } case OpCodes.ISNLT: { int a = (int)((ii >> 16) & 0xFF); int b = (int)((ii >> 8) & 0xFF); if (!(CompareValue(values[a], values[b]) < 0)) { pc += (sbyte)(byte)(ii & 0xFF); } break; } case OpCodes.ISNLE: { int a = (int)((ii >> 16) & 0xFF); int b = (int)((ii >> 8) & 0xFF); if (!(CompareValue(values[a], values[b]) <= 0)) { pc += (sbyte)(byte)(ii & 0xFF); } break; } case OpCodes.ISEQ: { int a = (int)((ii >> 16) & 0xFF); int b = (int)((ii >> 8) & 0xFF); if (CompareValueEqual(values[a], values[b])) { pc += (sbyte)(byte)(ii & 0xFF); } break; } case OpCodes.ISNE: { int a = (int)((ii >> 16) & 0xFF); int b = (int)((ii >> 8) & 0xFF); //!(cmp == 0) will return true for NaN comparison. if (!CompareValueEqual(values[a], values[b])) { pc += (sbyte)(byte)(ii & 0xFF); } break; } case OpCodes.ISTC: { int a = (int)((ii >> 16) & 0xFF); int b = (int)((ii >> 8) & 0xFF); if (values[b].ToBoolVal()) { values[a] = values[b]; pc += (sbyte)(byte)(ii & 0xFF); } break; } case OpCodes.ISFC: { int a = (int)((ii >> 16) & 0xFF); int b = (int)((ii >> 8) & 0xFF); if (!values[b].ToBoolVal()) { values[a] = values[b]; pc += (sbyte)(byte)(ii & 0xFF); } break; } case OpCodes.MOV: { int a = (int)((ii >> 16) & 0xFF); int b = (int)((ii >> 8) & 0xFF); values[a] = values[b]; break; } case OpCodes.NOT: { int a = (int)((ii >> 16) & 0xFF); int b = (int)((ii >> 8) & 0xFF); values[a] = values[b].ToBoolVal() ? TypedValue.False : TypedValue.True; break; } case OpCodes.NEG: { int a = (int)((ii >> 16) & 0xFF); int b = (int)((ii >> 8) & 0xFF); switch (values[b].Type) { case VMSpecializationType.Int: values[a] = TypedValue.MakeInt(-values[b].IntVal); break; case VMSpecializationType.Double: values[a] = TypedValue.MakeDouble(-values[b].DoubleVal); break; default: values[a] = UnaryNeg(values[b]); break; } break; } case OpCodes.LEN: { int a = (int)((ii >> 16) & 0xFF); int b = (int)((ii >> 8) & 0xFF); values[a] = UnaryLen(values[b]); break; } case OpCodes.ADD: { int a = (int)((ii >> 16) & 0xFF); int b = (int)((ii >> 8) & 0xFF); int c = (int)(ii & 0xFF); values[a] = Add(values[b], values[c]); break; } case OpCodes.ADD_D: { var a = (byte)(ii >> 16); var b = (byte)(ii >> 8); var c = (byte)ii; values[a].Number = values[b].Number + values[c].Number; break; } case OpCodes.SUB: { int a = (int)((ii >> 16) & 0xFF); int b = (int)((ii >> 8) & 0xFF); int c = (int)(ii & 0xFF); values[a] = Sub(values[b], values[c]); break; } case OpCodes.MUL: { int a = (int)((ii >> 16) & 0xFF); int b = (int)((ii >> 8) & 0xFF); int c = (int)(ii & 0xFF); values[a] = Mul(values[b], values[c]); break; } case OpCodes.DIV: { int a = (int)((ii >> 16) & 0xFF); int b = (int)((ii >> 8) & 0xFF); int c = (int)(ii & 0xFF); values[a] = Div(values[b], values[c]); break; } case OpCodes.MOD: { int a = (int)((ii >> 16) & 0xFF); int b = (int)((ii >> 8) & 0xFF); int c = (int)(ii & 0xFF); values[a] = Mod(values[b], values[c]); break; } case OpCodes.POW: { int a = (int)((ii >> 16) & 0xFF); int b = (int)((ii >> 8) & 0xFF); int c = (int)(ii & 0xFF); values[a] = Pow(values[b], values[c]); break; } case OpCodes.CAT: { int a = (int)((ii >> 16) & 0xFF); int b = (int)((ii >> 8) & 0xFF); int c = (int)(ii & 0xFF); var sb = thread.StringBuilder; sb.Clear(); for (int i = b; i < c; ++i) { WriteString(sb, values[i]); } values[a] = TypedValue.MakeString(sb.ToString()); break; } case OpCodes.K: { int a = (int)((ii >> 16) & 0xFF); int b = (int)((ii >> 8) & 0xFF); values[a] = proto.Constants[b]; break; } case OpCodes.K_D: { int a = (int)((ii >> 16) & 0xFF); int b = (int)((ii >> 8) & 0xFF); values[a].Number = proto.Constants[b].Number; break; } case OpCodes.UGET: { int a = (int)((ii >> 16) & 0xFF); int b = (int)((ii >> 8) & 0xFF); int c = (int)(ii & 0xFF); values[a] = values.GetUpvalue(b, c); break; } case OpCodes.USET: { int a = (int)((ii >> 16) & 0xFF); int b = (int)((ii >> 8) & 0xFF); int c = (int)(ii & 0xFF); values.GetUpvalue(b, c) = values[a]; break; } case OpCodes.UNEW: { int a = (int)((ii >> 16) & 0xFF); int b = (int)((ii >> 8) & 0xFF); values[a].Object = new TypedValue[b]; //Type not set. break; } case OpCodes.FNEW: { int a = (int)((ii >> 16) & 0xFF); int b = (int)((ii >> 8) & 0xFF); var(closureProto, upvalLists) = proto.ChildFunctions[b]; var nclosure = new LClosure { Proto = closureProto, UpvalLists = new TypedValue[upvalLists.Length][], }; for (int i = 0; i < upvalLists.Length; ++i) { nclosure.UpvalLists[i] = (TypedValue[])values[upvalLists[i]].Object; } values[a] = TypedValue.MakeLClosure(nclosure); break; } case OpCodes.TNEW: { int a = (int)((ii >> 16) & 0xFF); values[a] = TypedValue.MakeTable(new Table()); break; } case OpCodes.TGET: { int a = (int)((ii >> 16) & 0xFF); int b = (int)((ii >> 8) & 0xFF); int c = (int)(ii & 0xFF); if (values[b].Object is Table t) { t.Get(values[c], out values[a]); } else { GetTable(ref values[b], ref values[c], ref values[a]); } break; } case OpCodes.TSET: { int a = (int)((ii >> 16) & 0xFF); int b = (int)((ii >> 8) & 0xFF); int c = (int)(ii & 0xFF); if (values[b].Object is Table t) { t.Set(values[c], values[a]); } else { SetTable(ref values[b], ref values[c], ref values[a]); } break; } case OpCodes.TINIT: { int a = (int)((ii >> 16) & 0xFF); int l1 = (int)((ii >> 8) & 0xFF); int l2 = (int)(ii & 0xFF); if (l2 == 0) { sig.AdjustLeft(in values, proto.SigTypes[l1]); } else { sig.Type = proto.SigTypes[l1]; sig.Offset = l2; } var span = values.Span.Slice(sig.Offset, sig.TotalSize); ((Table)values[a].Object).SetSequence(span, sig.Type); sig.Clear(); break; } case OpCodes.CALL: case OpCodes.CALLC: { //Similar logic is also used in FORG. Be consistent whenever CALL/CALLC is updated. //CALL/CALLC uses 2 uints. int f = (int)((ii >> 16) & 0xFF); int l1 = (int)((ii >> 8) & 0xFF); int l2 = (int)(ii & 0xFF); ii = inst[pc++]; int r1 = (int)((ii >> 16) & 0xFF); var retHasVararg = proto.SigTypes[r1].Vararg.HasValue; frame.Data.PC = pc; //Adjust current sig block at the left side. //This allows to merge other arguments that have already been pushed before. var argSig = proto.SigTypes[l1]; if (l2 == 0) { sig.AdjustLeft(in values, argSig); } else { sig.Offset = l2; sig.Type = argSig; } var sigStart = sig.Offset; var newFuncP = values[f].Object; if (newFuncP is LClosure lc) { //Save state. frame.Data.SigOffset = sigStart; thread.ClosureStack.Push(closure); //Setup proto, closure, and stack. closure = lc; proto = lc.Proto; frame = thread.AllocateNextFrame(frame, sig.Offset, sig.TotalSize, proto.StackSize, retHasVararg); sig.Offset = 0; goto enterNewLuaFrame; } nonLuaFunc = newFuncP; goto callNonLuaFunction; } case OpCodes.VARG: case OpCodes.VARGC: { int pos = (int)((ii >> 16) & 0xFF); int r1 = (int)((ii >> 8) & 0xFF); int rx = (sbyte)(byte)(ii & 0xFF); //This should never write outside frame's range, because when we allocated this frame, //the size was calculated as proto's requirement + argument total size. //TODO this only works for unspecialized arguments (adjustment can increase total length) Debug.Assert(pos + frame.Data.VarargInfo.VarargLength <= values.Span.Length); for (int i = 0; i < frame.Data.VarargInfo.VarargLength; ++i) { values[pos + i] = thread.VarargStack[frame.Data.VarargInfo.VarargStart + i]; } //Overwrite sig as a vararg. Debug.Assert(proto.VarargSig.FixedSize == 0); sig.Type = proto.VarargSig; sig.Offset = pos; sig.VLength = frame.Data.VarargInfo.VarargLength; //Then adjust to requested (this is needed in assignment statement). if (rx >= 0) { //Fast path. if (sig.VLength >= rx) { sig.VLength -= rx; } else { values.Span.Slice(sig.Offset + sig.VLength, rx - sig.VLength).Fill(TypedValue.Nil); sig.VLength = 0; } sig.Type = proto.SigTypes[r1]; } else { var adjustmentSuccess = sig.AdjustRight(in values, proto.SigTypes[r1]); Debug.Assert(adjustmentSuccess); } if (!proto.SigTypes[r1].Vararg.HasValue) { sig.DiscardVararg(in values); } if ((OpCodes)(ii >> 24) == OpCodes.VARGC) { sig.Clear(); } break; } case OpCodes.VARG1: { int a = (int)((ii >> 16) & 0xFF); if (frame.Data.VarargInfo.VarargLength == 0) { values[a] = TypedValue.Nil; } else { values[a] = thread.VarargStack[frame.Data.VarargInfo.VarargStart]; } break; } case OpCodes.JMP: { pc += (short)(ushort)(ii & 0xFFFF); break; } case OpCodes.FORI: { int a = (int)((ii >> 16) & 0xFF); ForceConvDouble(ref values[a]); ForceConvDouble(ref values[a + 1]); ForceConvDouble(ref values[a + 2]); //Treat the values as double. ref var n1 = ref values[a].Number; ref var n2 = ref values[a + 1].Number; ref var n3 = ref values[a + 2].Number; if (n3 > 0) { if (!(n1 <= n2)) { pc += (short)(ushort)(ii & 0xFFFF); } } else { if (!(n1 >= n2)) { pc += (short)(ushort)(ii & 0xFFFF); } } break; }