Skip to content

Commit b3dcd3f

Browse files
committed
disable LOADNIL code generation optimisation
The `AddLoadNil` method used to merge multiple consecutive LOADNIL instructions of consecutive registers into a single LOADNIL instruction, but it caused issues when the merged instructions were JMP targets, and so generated invalid code; so the merging functionality has been removed. It is safe to merge the LOADNIL instructions under certain conditions, but additional logic / complexity would be needed.
1 parent ccacf66 commit b3dcd3f

File tree

2 files changed

+81
-52
lines changed

2 files changed

+81
-52
lines changed

compile.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -236,12 +236,13 @@ func (cd *codeStore) PropagateMV(top int, save *int, reg *int, inc int) {
236236
}
237237

238238
func (cd *codeStore) AddLoadNil(a, b, line int) {
239-
last := cd.Last()
240-
if opGetOpCode(last) == OP_LOADNIL && (opGetArgB(last)+1) == a {
241-
cd.SetB(cd.LastPC(), b)
242-
} else {
243-
cd.AddABC(OP_LOADNIL, a, b, 0, line)
244-
}
239+
// this method used to merge multiple consecutive LOADNIL instructions
240+
// of consecutive registers into a single LOADNIL instruction, but it
241+
// caused issues when the merged instructions were JMP targets, and so
242+
// generated invalid code; so the merging functionality has been removed.
243+
// It is safe to merge the LOADNIL instructions under certain conditions,
244+
// but additional logic / complexity would be needed here.
245+
cd.AddABC(OP_LOADNIL, a, b, 0, line)
245246
}
246247

247248
func (cd *codeStore) SetOpCode(pc int, v int) {

script_test.go

Lines changed: 74 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"fmt"
55
"os"
66
"runtime"
7-
"strings"
87
"sync/atomic"
98
"testing"
109
"time"
@@ -148,6 +147,33 @@ func TestLua(t *testing.T) {
148147
testScriptDir(t, luaTests, "_lua5.1-tests")
149148
}
150149

150+
func TestMergingLoadNilBug2(t *testing.T) {
151+
// there was a bug where the LOADNIL merging optimisation would merge LOADNILs that were the targets of
152+
// JMP instructions, causing the JMP to jump to the wrong location and breaking the logic and resulting in
153+
// a panic.
154+
s := `
155+
id = "foo"
156+
157+
function get_def()
158+
return {}
159+
end
160+
161+
function test()
162+
local def = id ~= nil and get_def() or nil
163+
if def ~= nil then
164+
print("def is not nil")
165+
end
166+
end
167+
168+
test()
169+
`
170+
L := NewState()
171+
defer L.Close()
172+
if err := L.DoString(s); err != nil {
173+
t.Error(err)
174+
}
175+
}
176+
151177
func TestMergingLoadNilBug(t *testing.T) {
152178
// there was a bug where a multiple load nils were being incorrectly merged, and the following code exposed it
153179
s := `
@@ -185,48 +211,50 @@ func TestMergingLoadNilBug(t *testing.T) {
185211
}
186212
}
187213

188-
func TestMergingLoadNil(t *testing.T) {
189-
// multiple nil assignments to consecutive registers should be merged
190-
s := `
191-
function test()
192-
local a = 0
193-
local b = 1
194-
local c = 2
195-
196-
-- this should generate just one LOADNIL byte code instruction
197-
a = nil
198-
b = nil
199-
c = nil
200-
201-
print(a,b,c)
202-
end
203-
204-
test()`
205-
206-
chunk, err := parse.Parse(strings.NewReader(s), "test")
207-
if err != nil {
208-
t.Fatal(err)
209-
}
210-
211-
compiled, err := Compile(chunk, "test")
212-
if err != nil {
213-
t.Fatal(err)
214-
}
215-
216-
if len(compiled.FunctionPrototypes) != 1 {
217-
t.Fatal("expected 1 function prototype")
218-
}
219-
220-
// there should be exactly 1 LOADNIL instruction in the byte code generated for the above
221-
// anymore, and the LOADNIL merging is not working correctly
222-
count := 0
223-
for _, instr := range compiled.FunctionPrototypes[0].Code {
224-
if opGetOpCode(instr) == OP_LOADNIL {
225-
count++
226-
}
227-
}
228-
229-
if count != 1 {
230-
t.Fatalf("expected 1 LOADNIL instruction, found %d", count)
231-
}
232-
}
214+
// This test is disabled because the LOADNIL merging optimisation has been disabled. See the comment in the
215+
// AddLoadNil() function for more information.
216+
//func TestMergingLoadNil(t *testing.T) {
217+
// // multiple nil assignments to consecutive registers should be merged
218+
// s := `
219+
// function test()
220+
// local a = 0
221+
// local b = 1
222+
// local c = 2
223+
//
224+
// -- this should generate just one LOADNIL byte code instruction
225+
// a = nil
226+
// b = nil
227+
// c = nil
228+
//
229+
// print(a,b,c)
230+
// end
231+
//
232+
// test()`
233+
//
234+
// chunk, err := parse.Parse(strings.NewReader(s), "test")
235+
// if err != nil {
236+
// t.Fatal(err)
237+
// }
238+
//
239+
// compiled, err := Compile(chunk, "test")
240+
// if err != nil {
241+
// t.Fatal(err)
242+
// }
243+
//
244+
// if len(compiled.FunctionPrototypes) != 1 {
245+
// t.Fatal("expected 1 function prototype")
246+
// }
247+
//
248+
// // there should be exactly 1 LOADNIL instruction in the byte code generated for the above
249+
// // anymore, and the LOADNIL merging is not working correctly
250+
// count := 0
251+
// for _, instr := range compiled.FunctionPrototypes[0].Code {
252+
// if opGetOpCode(instr) == OP_LOADNIL {
253+
// count++
254+
// }
255+
// }
256+
//
257+
// if count != 1 {
258+
// t.Fatalf("expected 1 LOADNIL instruction, found %d", count)
259+
// }
260+
//}

0 commit comments

Comments
 (0)