Esempio n. 1
0
        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);
        }
Esempio n. 2
0
        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;
                }