mirror of
https://github.com/luau-lang/luau.git
synced 2024-12-13 21:40:43 +00:00
b2f9f53ae3
- Fix DeprecatedGlobal warning text in cases when the global is deprecated without a suggested alternative - Fix an off-by-one error in type error text for incorrect use of string.format - Reduce stack consumption further during parsing, hopefully eliminating stack overflows during parsing/compilation for good - Mark interpolated string support as experimental (requires --fflags=LuauInterpolatedStringBaseSupport to enable) - Simplify garbage collection treatment of upvalues, reducing cache misses during sweeping stage and reducing the cost of upvalue assignment (SETUPVAL); supersedes #643 - Simplify garbage collection treatment of sleeping threads - Simplify sweeping of alive threads, reducing cache misses during sweeping stage - Simplify management of string buffers, removing redundant linked list operations
353 lines
8 KiB
Lua
353 lines
8 KiB
Lua
-- This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
|
-- This file is based on Lua 5.x tests -- https://github.com/lua/lua/tree/master/testes
|
|
print('testing garbage collection')
|
|
|
|
collectgarbage()
|
|
|
|
_G["while"] = 234
|
|
|
|
limit = 5000
|
|
|
|
|
|
|
|
contCreate = 0
|
|
|
|
print('tables')
|
|
while contCreate <= limit do
|
|
local a = {}; a = nil
|
|
contCreate = contCreate+1
|
|
end
|
|
|
|
a = "a"
|
|
|
|
contCreate = 0
|
|
print('strings')
|
|
while contCreate <= limit do
|
|
a = contCreate .. "b";
|
|
a = string.gsub(a, '(%d%d*)', string.upper)
|
|
a = "a"
|
|
contCreate = contCreate+1
|
|
end
|
|
|
|
|
|
contCreate = 0
|
|
|
|
a = {}
|
|
|
|
print('functions')
|
|
function a:test ()
|
|
while contCreate <= limit do
|
|
loadstring(string.format("function temp(a) return 'a%d' end", contCreate))()
|
|
assert(temp() == string.format('a%d', contCreate))
|
|
contCreate = contCreate+1
|
|
end
|
|
end
|
|
|
|
a:test()
|
|
|
|
-- collection of functions without locals, globals, etc.
|
|
do local f = function () end end
|
|
|
|
|
|
print('long strings')
|
|
x = "01234567890123456789012345678901234567890123456789012345678901234567890123456789"
|
|
assert(string.len(x)==80)
|
|
s = ''
|
|
n = 0
|
|
k = 300
|
|
while n < k do s = s..x; n=n+1; j=tostring(n) end
|
|
assert(string.len(s) == k*80)
|
|
s = string.sub(s, 1, 20000)
|
|
s, i = string.gsub(s, '(%d%d%d%d)', math.sin)
|
|
assert(i==20000/4)
|
|
s = nil
|
|
x = nil
|
|
|
|
assert(_G["while"] == 234)
|
|
|
|
|
|
local bytes = gcinfo()
|
|
while 1 do
|
|
local nbytes = gcinfo()
|
|
if nbytes < bytes then break end -- run until gc
|
|
bytes = nbytes
|
|
a = {}
|
|
end
|
|
|
|
|
|
local function dosteps (siz)
|
|
collectgarbage()
|
|
collectgarbage("stop")
|
|
local a = {}
|
|
for i=1,100 do a[i] = {{}}; local b = {} end
|
|
local x = gcinfo()
|
|
local i = 0
|
|
repeat
|
|
i = i+1
|
|
until collectgarbage("step", siz)
|
|
assert(gcinfo() < x)
|
|
return i
|
|
end
|
|
|
|
assert(dosteps(0) > 10)
|
|
assert(dosteps(6) < dosteps(2))
|
|
assert(dosteps(10000) == 1)
|
|
-- assert(collectgarbage("step", 1000000) == true)
|
|
-- assert(collectgarbage("step", 1000000))
|
|
|
|
|
|
do
|
|
local x = gcinfo()
|
|
collectgarbage()
|
|
collectgarbage("stop")
|
|
repeat
|
|
local a = {}
|
|
until gcinfo() > 1000
|
|
collectgarbage("restart")
|
|
repeat
|
|
local a = {}
|
|
until gcinfo() < 1000
|
|
end
|
|
|
|
lim = 15
|
|
a = {}
|
|
-- fill a with `collectable' indices
|
|
for i=1,lim do a[{}] = i end
|
|
b = {}
|
|
for k,v in pairs(a) do b[k]=v end
|
|
-- remove all indices and collect them
|
|
for n in pairs(b) do
|
|
a[n] = nil
|
|
assert(type(n) == 'table' and next(n) == nil)
|
|
collectgarbage()
|
|
end
|
|
b = nil
|
|
collectgarbage()
|
|
for n in pairs(a) do error("cannot be here") end
|
|
for i=1,lim do a[i] = i end
|
|
for i=1,lim do assert(a[i] == i) end
|
|
|
|
|
|
print('weak tables')
|
|
a = {}; setmetatable(a, {__mode = 'k'});
|
|
-- fill a with some `collectable' indices
|
|
for i=1,lim do a[{}] = i end
|
|
-- and some non-collectable ones
|
|
for i=1,lim do local t={}; a[t]=t end
|
|
for i=1,lim do a[i] = i end
|
|
for i=1,lim do local s=string.rep('@', i); a[s] = s..'#' end
|
|
collectgarbage()
|
|
local i = 0
|
|
for k,v in pairs(a) do assert(k==v or k..'#'==v); i=i+1 end
|
|
assert(i == 3*lim)
|
|
|
|
a = {}; setmetatable(a, {__mode = 'v'});
|
|
a[1] = string.rep('b', 21)
|
|
collectgarbage()
|
|
assert(a[1]) -- strings are *values*
|
|
a[1] = nil
|
|
-- fill a with some `collectable' values (in both parts of the table)
|
|
for i=1,lim do a[i] = {} end
|
|
for i=1,lim do a[i..'x'] = {} end
|
|
-- and some non-collectable ones
|
|
for i=1,lim do local t={}; a[t]=t end
|
|
for i=1,lim do a[i+lim]=i..'x' end
|
|
collectgarbage()
|
|
local i = 0
|
|
for k,v in pairs(a) do assert(k==v or k-lim..'x' == v); i=i+1 end
|
|
assert(i == 2*lim)
|
|
|
|
a = {}; setmetatable(a, {__mode = 'vk'});
|
|
local x, y, z = {}, {}, {}
|
|
-- keep only some items
|
|
a[1], a[2], a[3] = x, y, z
|
|
a[string.rep('$', 11)] = string.rep('$', 11)
|
|
-- fill a with some `collectable' values
|
|
for i=4,lim do a[i] = {} end
|
|
for i=1,lim do a[{}] = i end
|
|
for i=1,lim do local t={}; a[t]=t end
|
|
collectgarbage()
|
|
assert(next(a) ~= nil)
|
|
local i = 0
|
|
for k,v in pairs(a) do
|
|
assert((k == 1 and v == x) or
|
|
(k == 2 and v == y) or
|
|
(k == 3 and v == z) or k==v);
|
|
i = i+1
|
|
end
|
|
assert(i == 4)
|
|
x,y,z=nil
|
|
collectgarbage()
|
|
assert(next(a) == string.rep('$', 11))
|
|
|
|
-- shrinking tables reduce their capacity; confirming the shrinking is difficult but we can at least test the surface level behavior
|
|
a = {}; setmetatable(a, {__mode = 'ks'})
|
|
for i=1,lim do a[{}] = i end
|
|
collectgarbage()
|
|
assert(next(a) == nil)
|
|
|
|
-- testing userdata
|
|
collectgarbage("stop") -- stop collection
|
|
local u = newproxy(true)
|
|
local s = 0
|
|
local a = {[u] = 0}; setmetatable(a, {__mode = 'vk'})
|
|
for i=1,10 do a[newproxy(true)] = i end
|
|
-- for k in pairs(a) do assert(getmetatable(k) == getmetatable(u)) end
|
|
local a1 = {}; for k,v in pairs(a) do a1[k] = v end
|
|
for k,v in pairs(a1) do a[v] = k end
|
|
for i =1,10 do assert(a[i]) end
|
|
getmetatable(u).a = a1
|
|
getmetatable(u).u = u
|
|
do
|
|
local u = u
|
|
getmetatable(u).__gc = function (o)
|
|
assert(a[o] == 10-s)
|
|
assert(a[10-s] == nil) -- udata already removed from weak table
|
|
assert(getmetatable(o) == getmetatable(u))
|
|
assert(getmetatable(o).a[o] == 10-s)
|
|
s=s+1
|
|
end
|
|
end
|
|
a1, u = nil
|
|
assert(next(a) ~= nil)
|
|
collectgarbage()
|
|
-- assert(s==11)
|
|
collectgarbage()
|
|
assert(next(a) == nil) -- finalized keys are removed in two cycles
|
|
|
|
|
|
-- __gc x weak tables
|
|
local u = newproxy(true)
|
|
setmetatable(getmetatable(u), {__mode = "v"})
|
|
getmetatable(u).__gc = function (o) os.exit(1) end -- cannot happen
|
|
collectgarbage()
|
|
|
|
local u = newproxy(true)
|
|
local m = getmetatable(u)
|
|
m.x = {[{0}] = 1; [0] = {1}}; setmetatable(m.x, {__mode = "kv"});
|
|
m.__gc = function (o)
|
|
assert(next(getmetatable(o).x) == nil)
|
|
m = 10
|
|
end
|
|
u, m = nil
|
|
collectgarbage()
|
|
-- assert(m==10)
|
|
|
|
|
|
-- errors during collection
|
|
u = newproxy(true)
|
|
getmetatable(u).__gc = function () error "!!!" end
|
|
u = nil
|
|
-- assert(not pcall(collectgarbage))
|
|
collectgarbage()
|
|
|
|
|
|
if not rawget(_G, "_soft") then
|
|
print("deep structures")
|
|
local a = {}
|
|
for i = 1,200000 do
|
|
a = {next = a}
|
|
end
|
|
collectgarbage()
|
|
end
|
|
|
|
-- create many threads with self-references and open upvalues
|
|
do
|
|
local thread_id = 0
|
|
local threads = {}
|
|
|
|
function fn(thread)
|
|
local x = {}
|
|
threads[thread_id] = function()
|
|
thread = x
|
|
end
|
|
coroutine.yield()
|
|
end
|
|
|
|
while thread_id < 1000 do
|
|
local thread = coroutine.create(fn)
|
|
coroutine.resume(thread, thread)
|
|
thread_id = thread_id + 1
|
|
end
|
|
|
|
collectgarbage()
|
|
|
|
-- ensure that we no longer have a lot of reachable threads for subsequent tests
|
|
threads = {}
|
|
end
|
|
|
|
-- create a userdata to be collected when state is closed
|
|
do
|
|
local newproxy,assert,type,print,getmetatable =
|
|
newproxy,assert,type,print,getmetatable
|
|
local u = newproxy(true)
|
|
local tt = getmetatable(u)
|
|
___Glob = {u} -- avoid udata being collected before program end
|
|
tt.__gc = function (o)
|
|
assert(getmetatable(o) == tt)
|
|
-- create new objects during GC
|
|
local a = 'xuxu'..(10+3)..'joao', {}
|
|
___Glob = o -- resurrect object!
|
|
newproxy(o) -- creates a new one with same metatable
|
|
print(">>> closing state " .. "<<<\n")
|
|
end
|
|
end
|
|
|
|
-- create several udata to raise errors when collected while closing state
|
|
do
|
|
local u = newproxy(true)
|
|
getmetatable(u).__gc = function (o) return o + 1 end
|
|
table.insert(___Glob, u) -- preserve udata until the end
|
|
for i = 1,10 do table.insert(___Glob, newproxy(true)) end
|
|
end
|
|
|
|
-- create threads that die together with their unmarked upvalues
|
|
do
|
|
local t = {}
|
|
|
|
for i = 1,100 do
|
|
local c = coroutine.wrap(function()
|
|
local uv = {i + 1}
|
|
local function f()
|
|
return uv[1] * 10
|
|
end
|
|
coroutine.yield(uv[1])
|
|
uv = {i + 2}
|
|
coroutine.yield(f())
|
|
end)
|
|
|
|
assert(c() == i + 1)
|
|
table.insert(t, c)
|
|
end
|
|
|
|
for i = 1,100 do
|
|
t[i] = nil
|
|
end
|
|
|
|
collectgarbage()
|
|
end
|
|
|
|
-- create a lot of threads with upvalues to force a case where full gc happens after we've marked some upvalues
|
|
do
|
|
local t = {}
|
|
for i = 1,100 do
|
|
local c = coroutine.wrap(function()
|
|
local uv = {i + 1}
|
|
local function f()
|
|
return uv[1] * 10
|
|
end
|
|
coroutine.yield(uv[1])
|
|
uv = {i + 2}
|
|
coroutine.yield(f())
|
|
end)
|
|
|
|
assert(c() == i + 1)
|
|
table.insert(t, c)
|
|
end
|
|
|
|
t = {}
|
|
|
|
collectgarbage()
|
|
end
|
|
|
|
return('OK')
|