tail call optimization

This commit is contained in:
Daniel Kang 2019-01-11 00:53:04 -08:00
parent d870ebf72e
commit b2a29342b1
3 changed files with 92 additions and 72 deletions

View file

@ -880,9 +880,9 @@ func (v *VM) callFunction(fn *objects.CompiledFunction, freeVars []*objects.Obje
// check if this is a tail-call (recursive call right before return)
curFrame := &(v.frames[v.framesIndex-1])
if fn == curFrame.fn {
if fn == curFrame.fn { // recursion
nextOp := compiler.Opcode(curFrame.fn.Instructions[curFrame.ip+1])
if nextOp == compiler.OpReturnValue ||
if nextOp == compiler.OpReturnValue || // tail call
(nextOp == compiler.OpPop &&
compiler.OpReturn == compiler.Opcode(curFrame.fn.Instructions[curFrame.ip+2])) {
@ -904,10 +904,7 @@ func (v *VM) callFunction(fn *objects.CompiledFunction, freeVars []*objects.Obje
// | ARG1 | <- BP for current function
// |--------|
for i := 0; i < numArgs; i++ {
v.stack[curFrame.basePointer+i] = v.stack[v.sp-numArgs+i]
}
copy(v.stack[curFrame.basePointer:], v.stack[v.sp-numArgs:v.sp])
v.sp -= numArgs
curFrame.ip = -1

View file

@ -194,19 +194,34 @@ out = outer() + g
out = f2();
`, 99)
// fibonacci
// recursion
expect(t, `
fib := func(x) {
if(x == 0) {
return 0;
if x == 0 {
return 0
} else if x == 1 {
return 1
} else {
if(x == 1) {
return 1;
} else {
return fib(x-1) + fib(x-2);
}
return fib(x-1) + fib(x-2)
}
};
out = fib(15);
`, 610)
}
out = fib(15)`, 610)
// TODO: currently recursion inside the local scope function definition is not supported.
// Workaround is to define the identifier first then assign the function like below.
// Want to fix this.
expect(t, `
func() {
fib := 0
fib = func(x) {
if x == 0 {
return 0
} else if x == 1 {
return 1
} else {
return fib(x-1) + fib(x-2)
}
}
out = fib(15)
}()`, 610)
}

View file

@ -4,74 +4,82 @@ import "testing"
func TestTailCall(t *testing.T) {
expect(t, `
fac := func(n, a) {
if n == 1 {
return a
fac := func(n, a) {
if n == 1 {
return a
}
return fac(n-1, n*a)
}
return fac(n-1, n*a)
}
out = fac(5, 1)`, 120)
out = fac(5, 1)`, 120)
expect(t, `
fac := func(n, a) {
if n == 1 {
return a
fac := func(n, a) {
if n == 1 {
return a
}
x := {foo: fac} // indirection for test
return x.foo(n-1, n*a)
}
x := {foo: fac} // indirection for test
return x.foo(n-1, n*a)
}
out = fac(5, 1)`, 120)
out = fac(5, 1)`, 120)
expect(t, `
fib := func(x, s) {
if x == 0 {
return 0 + s
} else if x == 1 {
return 1 + s
fib := func(x, s) {
if x == 0 {
return 0 + s
} else if x == 1 {
return 1 + s
}
return fib(x-1, fib(x-2, s))
}
return fib(x-1, fib(x-2, s))
}
out = fib(15, 0)`, 610)
out = fib(15, 0)`, 610)
expect(t, `
fib := func(n, a, b) {
if n == 0 {
return a
} else if n == 1 {
return b
}
return fib(n-1, b, a + b)
}
out = fib(15, 0, 1)`, 610)
// global variable and no return value
expect(t, `
out = 0
foo := func(a) {
if a == 0 {
return
}
out += a
foo(a-1)
}
foo(10)`, 55)
out = 0
foo := func(a) {
if a == 0 {
return
}
out += a
foo(a-1)
}
foo(10)`, 55)
// TODO: see comment inside the code
expect(t, `
f1 := func() {
f2 := undefined // TODO: this is really inconvenient
f2 = func(n, s) {
if n == 0 { return s }
return f2(n-1, n + s)
f1 := func() {
f2 := 0 // TODO: this might be fixed in the future
f2 = func(n, s) {
if n == 0 { return s }
return f2(n-1, n + s)
}
return f2(5, 0)
}
return f2(5, 0)
out = f1()`, 15)
}
out = f1()`, 15)
// tail call with free vars
// expect(t, `
//f1 := func() {
// a := 10
// f2 := undefined
// f2 = func(n, s) {
// a += s
// if n == 0 {
// return
// }
// f2(n-1, n + s)
// }
// return f2
//}
//out = f1()(5, 0)`, 25)
// tail call with free vars
func TestTailCallFreeVars(t *testing.T) {
expect(t, `
func() {
a := 10
f2 := 0
f2 = func(n, s) {
if n == 0 {
return s + a
}
return f2(n-1, n+s)
}
out = f2(5, 0)
}()`, 25)
}