gopher-lua icon indicating copy to clipboard operation
gopher-lua copied to clipboard

Support Hooks in GopherLua

Open ysweid opened this issue 8 years ago • 5 comments

This issue is created to introduce hooks in gopher-lua.

Implementation:

  • A Hooker interface will be created, that has call and String functions.
type Hooker interface {
	call(L *LState, cf *callFrame)
	String() string
}
  • lhook, rhook, chook and cthook are Hooker typed variables that will be declared in LState struct. and prevline to keep track of last executed line.
type LState struct {
	..............
        chook       Hooker
        rhook       Hooker
	lhook       Hooker
	cthook      Hooker
	prevline    int
}
  • The call function will be used to execute the behaviour of the hook. LineHook call implementation.
func (lh *LHook) call(L *LState, cf *callFrame) {
	currentline := cf.Fn.Proto.DbgSourcePositions[cf.Pc-1]
	if currentline != 0 && cf.Fn != lh.callback && currentline != L.prevline {
		L.reg.Push(lh.callback)
		L.reg.Push(LString("l"))
		L.reg.Push(LNumber(currentline))
		L.callR(2, 0, -1)
		L.prevline = currentline
	}
}
  • VM API: event may contain "lcr" (any not recognised letter will return error) and let count param <= 0 to keep count hook disabled.
err := L.SetHook(callback *LFunction, event string, count int)

Progress: I have already implemented the four hooks. Line hook required me to fix a bug (I guess) in compile.go that was ignoring the table items lines when the table expression gets compiled.

Test case: hook.lua

function tracer(event, line)
    print("from hook", event, line)
end

debug.sethook(tracer, "l")


local obj = {
    foo="bar",
    baz="qux"
}

for k, v in pairs(obj) do
    print(k, v)
end

local x = 5
while x == 5 do
    x = 6
end

if x == 6 then
    print(true)
end

if x == 7 then
    print(false)
elseif x == 8 then
    print(false)
else
    print(true)
end

local y = 0
repeat
    y = y + 1
until y == 3

print(y)

Glua's output:

from hook	l	8
from hook	l	9
from hook	l	10
from hook	l	13
from hook	l	14
baz	qux
from hook	l	13
from hook	l	14
foo	bar
from hook	l	13
from hook	l	17
from hook	l	18
from hook	l	19
from hook	l	20
from hook	l	18
from hook	l	22
from hook	l	23
true
from hook	l	26
from hook	l	28
from hook	l	31
true
from hook	l	34
from hook	l	36
from hook	l	37
from hook	l	36
from hook	l	37
from hook	l	36
from hook	l	37
from hook	l	39
3

Lua output:

from hook	line	8
from hook	line	9
from hook	line	10
from hook	line	13
from hook	line	14
foo	bar
from hook	line	13
from hook	line	14
baz	qux
from hook	line	13
from hook	line	17
from hook	line	18
from hook	line	19
from hook	line	18
from hook	line	22
from hook	line	23
true
from hook	line	26
from hook	line	28
from hook	line	31
true
from hook	line	34
from hook	line	36
from hook	line	37
from hook	line	36
from hook	line	37
from hook	line	36
from hook	line	37
from hook	line	39
3

Outputs differences:

  • It is interesting that Lua looped over the obj table from the last item to the first, while Glua started from the first item. (Not sure whether it's matter of pairs implementation or for in loop).

  • Lua jumps over end label of while statement, while Glua counts it.

  • As for call, return hooks, I can tell that lua is making way more calls to the callback than glua. It's either I need to call the hooks in places I'm not aware of, or we're making less calls to OP_RETURN and OP_CALL.

Missing:

  • ~return, call, count hooks.~
  • Reset hooks.
  • Tests.
  • Benchmark.

Compiled and tested on:

  • Intel Core i5 Skylake 6360U processor (MacBook Pro 13" 2016 base model).
  • Go version go1.8.3 darwin/amd64

@yuin I pushed a PR for early review, and to let you confirm the implementation.

ysweid avatar Jun 28 '17 15:06 ysweid

return, call and count hooks have been implemented.

Initial benchmark shows that if false condition has no effect at all.

ysweid avatar Jun 28 '17 19:06 ysweid

Close issue?

ghost avatar Dec 22 '17 04:12 ghost

any progress? need this pr

edolphin-ydf avatar Oct 29 '19 12:10 edolphin-ydf

Indeed it would be very nice to see the getting merged and I think this would be a very useful feature. Currently its a bit hard to debug scripts running on gopher-lua.

guerillagrow avatar Dec 10 '19 10:12 guerillagrow

@ysweid Since I need to analyse glua performance issues, I tried to use your hook implementation and had some problems: 1.bug found:in

https://github.com/ysweid/gopher-lua/blob/39e4eebec3f18f3d5de4d9ccc1410f4e5d37f90f/hook.go#L24

.You need to add a variable or recursively search for parent to avoid this: when a line hook 's callback function contains a function call, it appears that the callback function itself triggers the line hook,code like this:(add isCalled variable)

if currentline != 0 && cf.Fn != lh.callback && currentline != L.prevline{
                lh.isCalled = true
		L.reg.Push(lh.callback)
		L.reg.Push(LString("line"))
		L.reg.Push(LNumber(currentline))
		L.callR(2, 0, -1)
		L.prevline = currentline
		lh.isCalled = false
}

or recursively search for parent and add a judgemental condition: !isFunctionInCallFrameChain(cf, rh.callback):

func isFunctionInCallFrameChain(cf *callFrame, fn *LFunction) bool {
	if cf.Fn == fn {
		return true
	}
	if cf.Parent != nil {
		return isFunctionInCallFrameChain(cf.Parent, fn)
	}
	return false
}

2. two questions 2.1 I tried to use luatrace tool in gopher-lua,several places appear to behave differently from lua using the c language: when line and call hook are both used,c-lua first trigger call hook and then line hook,but glua is the opposite. I tried to change this but failed: my change like this:

return_value:=jumpTable[int(inst>>26)](L, inst, baseframe)

if L.lhook != nil {
  L.lhook.call(L, cf)
}
if L.cthook != nil {
  L.cthook.call(L, cf)
}		
if return_value == 1 {
  return
}

The reason I changed it is that I noticed that the jumpTable is only used here, and the call hook is added to the call function in the jumpTable,but it doesn't work.I'm curious as to why it didn't work. 2.2 Finally,I found in tailcall condition(such as this: return func()): gopher-lua looks to optimise all the tailcalls into one function with only once return hook trigger,but in c-lua it will trigger return hook whether or not is tailcall,I don't know where to change this,maybe in jumpTable? (because I noticed that there's only OP_TAILCALL function in jumpTable, do not have OP_TAILRETURN)?

wufei-png avatar Jan 22 '24 06:01 wufei-png