diff --git a/src/hx/cppia/Cppia.h b/src/hx/cppia/Cppia.h index 42085ed56..468681fac 100644 --- a/src/hx/cppia/Cppia.h +++ b/src/hx/cppia/Cppia.h @@ -304,6 +304,9 @@ struct ScriptCallable : public CppiaDynamicExpr #ifdef CPPIA_JIT void compile(); void genDefaults(CppiaCompiler *compiler); + #ifdef HXCPP_DEBUGGER + void genZeroLocals(CppiaCompiler *compiler); + #endif void genArgs(CppiaCompiler *compiler, CppiaExpr *inThis, Expressions &inArgs, const JitVal &inThisVal); void genCode(CppiaCompiler *compiler,const JitVal &inDest=JitVal(),ExprType type=etNull) HXCPP_OVERRIDE; #endif diff --git a/src/hx/cppia/CppiaFunction.cpp b/src/hx/cppia/CppiaFunction.cpp index 005977b36..3865ff07e 100644 --- a/src/hx/cppia/CppiaFunction.cpp +++ b/src/hx/cppia/CppiaFunction.cpp @@ -201,11 +201,30 @@ CppiaExpr *ScriptCallable::link(CppiaModule &inModule) } #ifdef HXCPP_STACK_SCRIPTABLE + +// The base holds 'this', then the arguments and locals at their stackPos +// (stackSize covers all of them). Interpreted (both vtable dispatch and +// CppiaClosure paths), 'this' and the arguments are pushed at ctx->frame, and +// runFunction then allocates the whole layout again before the StackFrame: +// base = ctx->frame; StackFrame (inStack) = base + sizeof(Object*) + argBytes + stackSize +// Jitted code pushes its StackFrame right after the layout: +// StackFrame (inStack) = base + stackSize +static unsigned char *scriptableBase(unsigned char *inStack, int stackSize, + const std::vector &args, bool inCompiled) +{ + if (inCompiled) + return inStack - stackSize; + int argBytes = 0; + for (int i = 0; i < (int)args.size(); i++) + argBytes += sTypeSize[args[i].argType]; + return inStack - stackSize - sizeof(hx::Object *) - argBytes; +} + void ScriptCallable::getScriptableVariables(unsigned char *inStack, Array outNames) { - unsigned char *frame = inStack - stackSize; + unsigned char *base = scriptableBase(inStack, stackSize, args, compiled!=0); - hx::Object *thizz = *(hx::Object **)frame; + hx::Object *thizz = *(hx::Object **)base; if (thizz) outNames->push(HX_CSTRING("this")); @@ -219,10 +238,10 @@ void ScriptCallable::getScriptableVariables(unsigned char *inStack, Arraysecond; if ( var->module->strings[ var->nameId]==inName) { - outValue = var->getInFrame(frame); + outValue = var->getInFrame(base); return true; } } @@ -243,13 +262,13 @@ bool ScriptCallable::getScriptableValue(unsigned char *inStack, String inName, : bool ScriptCallable::setScriptableValue(unsigned char *inStack, String inName, ::Dynamic inValue) { - unsigned char *frame = inStack - stackSize; + unsigned char *base = scriptableBase(inStack, stackSize, args, compiled!=0); for(CppiaStackVarMap::iterator i=varMap.begin();i!=varMap.end();++i) { CppiaStackVar *var = i->second; if ( var->module->strings[ var->nameId]==inName) { - var->setInFrame(frame,inValue); + var->setInFrame(base,inValue); return true; } } @@ -364,6 +383,27 @@ void ScriptCallable::genArgs(CppiaCompiler *compiler, CppiaExpr *inThis, Express } +#ifdef HXCPP_DEBUGGER +static void SLJIT_CALL zeroLocals(unsigned char *inLocals, void *inSize) +{ + memset(inLocals, 0, (size_t)inSize); +} + +// The interpreter zeroes a frame's locals; jitted code writes each before +// reading it, but the debugger can read any of them at any time +void ScriptCallable::genZeroLocals(CppiaCompiler *compiler) +{ + int argEnd = sizeof(void *); + for(int i=0;i argEnd) + { + compiler->add(sJitArg1.as(jtPointer), sJitFrame.as(jtPointer), argEnd ); + compiler->callNative( (void *)zeroLocals, sJitArg1.as(jtPointer), JitVal((void *)(size_t)(stackSize - argEnd)) ); + } +} +#endif + void ScriptCallable::genDefaults(CppiaCompiler *compiler) { if (hasDefaults) @@ -646,10 +686,18 @@ void ScriptCallable::compile() CppiaCompiler *compiler = CppiaCompiler::create(size); // First pass calculates size... + #ifdef HXCPP_DEBUGGER + genZeroLocals(compiler); + #endif genDefaults(compiler); #ifdef HXCPP_STACK_TRACE compiler->move(sJitFrame.star(jtPointer)+(offsetof(StackFrame,position) + stackSize), (void *)&position ); + #if defined(HXCPP_STACK_LINE) && defined(HXCPP_DEBUGGER) + // As the StackFrame constructor does - the debugger walks these lists + compiler->move(sJitFrame.star(jtPointer)+(offsetof(StackFrame,variables) + stackSize), (void *)0 ); + compiler->move(sJitFrame.star(jtPointer)+(offsetof(StackFrame,catchables) + stackSize), (void *)0 ); + #endif compiler->add(sJitArg1.as(jtPointer), sJitFrame.as(jtPointer), stackSize ); compiler->callNative( (void *)pushFrame, sJitCtx.as(jtPointer), sJitArg1.as(jtPointer) ); @@ -664,10 +712,18 @@ void ScriptCallable::compile() compiler->beginGeneration(1); // Second pass does the job + #ifdef HXCPP_DEBUGGER + genZeroLocals(compiler); + #endif genDefaults(compiler); #ifdef HXCPP_STACK_TRACE compiler->move(sJitFrame.star(jtPointer)+(offsetof(StackFrame,position) + stackSize), (void *)&position ); + #if defined(HXCPP_STACK_LINE) && defined(HXCPP_DEBUGGER) + // As the StackFrame constructor does - the debugger walks these lists + compiler->move(sJitFrame.star(jtPointer)+(offsetof(StackFrame,variables) + stackSize), (void *)0 ); + compiler->move(sJitFrame.star(jtPointer)+(offsetof(StackFrame,catchables) + stackSize), (void *)0 ); + #endif compiler->add(sJitArg1.as(jtPointer), sJitFrame.as(jtPointer), stackSize ); compiler->callNative( (void *)pushFrame, sJitCtx.as(jtPointer), sJitArg1.as(jtPointer) ); #endif diff --git a/test/regression/CppiaDebuggerFrames/Inspect.hx b/test/regression/CppiaDebuggerFrames/Inspect.hx new file mode 100644 index 000000000..6a94f0137 --- /dev/null +++ b/test/regression/CppiaDebuggerFrames/Inspect.hx @@ -0,0 +1,28 @@ +import cpp.vm.Debugger; + +class Inspect { + static function frame(name:String):Int { + var thread = Debugger.getCurrentThreadNumber(); + var stack = Debugger.getThreadInfo(thread, true).stack; + for (i in 0...stack.length) + if (stack[i].functionName == name) + return i; + return -1; + } + + public static function caller(name:String):String { + var thread = Debugger.getCurrentThreadNumber(); + var i = frame(name); + if (i < 0) + return "no frame for " + name; + var names = Debugger.getStackVariables(thread, i, true); + names.sort(Reflect.compare); + var values = [for (n in names) n == "this" ? "this=" + Std.string(Debugger.getStackVariableValue(thread, i, n, true) != null) + : n + "=" + Std.string(Debugger.getStackVariableValue(thread, i, n, true))]; + return values.join(" "); + } + + public static function set(name:String, variable:String, value:Dynamic) { + Debugger.setStackVariableValue(Debugger.getCurrentThreadNumber(), frame(name), variable, value, true); + } +} diff --git a/test/regression/CppiaDebuggerFrames/Main.hx b/test/regression/CppiaDebuggerFrames/Main.hx new file mode 100644 index 000000000..79f6a6c60 --- /dev/null +++ b/test/regression/CppiaDebuggerFrames/Main.hx @@ -0,0 +1,16 @@ +// hx::EnableJit, which Host.enableJit calls +@:cppInclude("hx/Scriptable.h") +class Main { + static function main() { + Inspect.caller(""); + var script = haxe.io.Path.join([haxe.io.Path.directory(Sys.programPath()), "script.cppia"]); + for (jit in [false, true]) { + cpp.cppia.Host.enableJit(jit); + try { + cpp.cppia.Host.runFile(script); + } catch (e:Dynamic) { + Sys.println("error: " + e); + } + } + } +} diff --git a/test/regression/CppiaDebuggerFrames/Script.hx b/test/regression/CppiaDebuggerFrames/Script.hx new file mode 100644 index 000000000..001c2480e --- /dev/null +++ b/test/regression/CppiaDebuggerFrames/Script.hx @@ -0,0 +1,46 @@ +class Script { + var label:String; + + function new() + label = "probe"; + + function probe(a:Int, b:String):String { + var c = a * 2; + var seen = Inspect.caller("probe"); + return seen + (c > 0 ? "" : ""); + } + + function mixed(i:Int, f:Float, s:String, j:Int, o:Array):String { + var k = i + j; + var seen = Inspect.caller("mixed"); + return seen + (k > 0 && f > 0 && s != null && o != null ? "" : ""); + } + + function defaults(a:Int, b:Int = 7, ?c:String):String { + var seen = Inspect.caller("defaults"); + return seen + (a + b > 0 && c == null ? "" : ""); + } + + function setter(n:Int):String { + var m = n; + Inspect.set("setter", "m", 99); + Inspect.set("setter", "n", 5); + return "m=" + m + " n=" + n; + } + + static function staticProbe(x:Float):String { + var y = x + 1; + var seen = Inspect.caller("staticProbe"); + return seen + (y > 0 ? "" : ""); + } + + static function main() { + var s = new Script(); + Sys.println("method: " + s.probe(21, "two")); + Sys.println("mixed: " + s.mixed(1, 2.5, "s", 3, [4])); + Sys.println("defaults: " + s.defaults(1)); + Sys.println("dynamic: " + Reflect.callMethod(s, Reflect.field(s, "mixed"), [5, 0.5, "d", 6, []])); + Sys.println("setter: " + s.setter(1)); + Sys.println("static: " + staticProbe(1.5)); + } +} diff --git a/test/regression/CppiaDebuggerFrames/build.hxml b/test/regression/CppiaDebuggerFrames/build.hxml new file mode 100644 index 000000000..587f63357 --- /dev/null +++ b/test/regression/CppiaDebuggerFrames/build.hxml @@ -0,0 +1,8 @@ +--main Script +-cppia bin/script.cppia +--next +--main Main +-cpp bin +-D scriptable +-D HXCPP_DEBUGGER +--dce no diff --git a/test/regression/CppiaDebuggerFrames/stdout.txt b/test/regression/CppiaDebuggerFrames/stdout.txt new file mode 100644 index 000000000..1e2aff7ea --- /dev/null +++ b/test/regression/CppiaDebuggerFrames/stdout.txt @@ -0,0 +1,12 @@ +method: a=21 b=two c=42 seen=null this=true +mixed: f=2.5 i=1 j=3 k=4 o=[4] s=s seen=null this=true +defaults: a=1 b=7 c=null seen=null this=true +dynamic: f=0.5 i=5 j=6 k=11 o=[] s=d seen=null this=true +setter: m=99 n=5 +static: seen=null x=1.5 y=2.5 +method: a=21 b=two c=42 seen=null this=true +mixed: f=2.5 i=1 j=3 k=4 o=[4] s=s seen=null this=true +defaults: a=1 b=7 c=null seen=null this=true +dynamic: f=0.5 i=5 j=6 k=11 o=[] s=d seen=null this=true +setter: m=99 n=5 +static: seen=null x=1.5 y=2.5