refactor(cache): documented the cache in preparation to upstream

This commit is contained in:
Folke Lemaitre 2023-03-14 12:56:58 +01:00
parent 5550f99271
commit 1a34636094
No known key found for this signature in database
GPG Key ID: 41F8B1FBACAE2040
3 changed files with 52 additions and 18 deletions

View File

@ -51,6 +51,10 @@ function Cache.normalize(path)
return path:sub(-1) == "/" and path:sub(1, -2) or path return path:sub(-1) == "/" and path:sub(1, -2) or path
end end
-- Gets the rtp excluding after directories.
-- The result is cached, and will be updated if the runtime path changes.
-- When called from a fast event, the cached value will be returned.
--- @return string[] rtp, boolean updated
---@private ---@private
function Cache.get_rtp() function Cache.get_rtp()
local start = uv.hrtime() local start = uv.hrtime()
@ -76,13 +80,17 @@ function Cache.get_rtp()
return Cache._rtp, updated return Cache._rtp, updated
end end
-- Returns the cache file name
---@param name string can be a module name, or a file name ---@param name string can be a module name, or a file name
---@return string file_name
---@private ---@private
function Cache.cache_file(name) function Cache.cache_file(name)
local ret = M.path .. "/" .. name:gsub("[/\\:]", "%%") local ret = M.path .. "/" .. name:gsub("[/\\:]", "%%")
return ret:sub(-4) == ".lua" and (ret .. "c") or (ret .. ".luac") return ret:sub(-4) == ".lua" and (ret .. "c") or (ret .. ".luac")
end end
-- Saves the cache entry for a given module or file
---@param name string module name or filename
---@param entry CacheEntry ---@param entry CacheEntry
---@private ---@private
function Cache.write(name, entry) function Cache.write(name, entry)
@ -99,6 +107,8 @@ function Cache.write(name, entry)
uv.fs_close(f) uv.fs_close(f)
end end
-- Loads the cache entry for a given module or file
---@param name string module name or filename
---@return CacheEntry? ---@return CacheEntry?
---@private ---@private
function Cache.read(name) function Cache.read(name)
@ -124,11 +134,13 @@ function Cache.read(name)
M.track("read", start) M.track("read", start)
end end
---@param modname string -- The `package.loaders` loader for lua files using the cache.
---@param modname string module name
---@return string|function
---@private ---@private
function Cache.loader(modname) function Cache.loader(modname)
local start = uv.hrtime() local start = uv.hrtime()
local modpath, hash = Cache.find(modname) local modpath, hash = M.find(modname)
if modpath then if modpath then
local chunk, err = M.load(modpath, { hash = hash }) local chunk, err = M.load(modpath, { hash = hash })
M.track("loader", start) M.track("loader", start)
@ -138,11 +150,13 @@ function Cache.loader(modname)
return "\nlazy_loader: module " .. modname .. " not found" return "\nlazy_loader: module " .. modname .. " not found"
end end
---@param modname string -- The `package.loaders` loader for libs
---@param modname string module name
---@return string|function
---@private ---@private
function Cache.loader_lib(modname) function Cache.loader_lib(modname)
local start = uv.hrtime() local start = uv.hrtime()
local modpath = Cache.find(modname, { patterns = jit.os:find("Windows") and { ".dll" } or { ".so" } }) local modpath = M.find(modname, { patterns = jit.os:find("Windows") and { ".dll" } or { ".so" } })
---@type function?, string? ---@type function?, string?
if modpath then if modpath then
-- Making function name in Lua 5.1 (see src/loadlib.c:mkfuncname) is -- Making function name in Lua 5.1 (see src/loadlib.c:mkfuncname) is
@ -160,6 +174,7 @@ function Cache.loader_lib(modname)
return "\nlazy_loader_lib: module " .. modname .. " not found" return "\nlazy_loader_lib: module " .. modname .. " not found"
end end
-- `loadfile` using the cache
---@param filename? string ---@param filename? string
---@param mode? "b"|"t"|"bt" ---@param mode? "b"|"t"|"bt"
---@param env? table ---@param env? table
@ -168,11 +183,16 @@ end
function Cache.loadfile(filename, mode, env) function Cache.loadfile(filename, mode, env)
local start = uv.hrtime() local start = uv.hrtime()
filename = Cache.normalize(filename) filename = Cache.normalize(filename)
mode = nil -- ignore mode, since we byte-compile the lua source files
local chunk, err = M.load(filename, { mode = mode, env = env }) local chunk, err = M.load(filename, { mode = mode, env = env })
M.track("loadfile", start) M.track("loadfile", start)
return chunk, err return chunk, err
end end
-- Checks whether two cache hashes are the same based on:
-- * file size
-- * mtime in seconds
-- * mtime in nanoseconds
---@param h1 CacheHash ---@param h1 CacheHash
---@param h2 CacheHash ---@param h2 CacheHash
---@private ---@private
@ -180,6 +200,7 @@ function Cache.eq(h1, h2)
return h1 and h2 and h1.size == h2.size and h1.mtime.sec == h2.mtime.sec and h1.mtime.nsec == h2.mtime.nsec return h1 and h2 and h1.size == h2.size and h1.mtime.sec == h2.mtime.sec and h1.mtime.nsec == h2.mtime.nsec
end end
-- Loads the given module path using the cache
---@param modpath string ---@param modpath string
---@param opts? {hash?: CacheHash, mode?: "b"|"t"|"bt", env?:table} ---@param opts? {hash?: CacheHash, mode?: "b"|"t"|"bt", env?:table}
---@return function?, string? error_message ---@return function?, string? error_message
@ -220,10 +241,11 @@ function M.load(modpath, opts)
return chunk, err return chunk, err
end end
-- Finds the module path for the given module name
---@param modname string ---@param modname string
---@param opts? CacheFindOpts ---@param opts? CacheFindOpts
---@return string? modpath, CacheHash? hash, CacheEntry? entry ---@return string? modpath, CacheHash? hash
function Cache.find(modname, opts) function M.find(modname, opts)
local start = uv.hrtime() local start = uv.hrtime()
opts = opts or {} opts = opts or {}
@ -231,12 +253,15 @@ function Cache.find(modname, opts)
local basename = modname:gsub("%.", "/") local basename = modname:gsub("%.", "/")
local idx = modname:find(".", 1, true) local idx = modname:find(".", 1, true)
-- HACK: fix incorrect require statements. Really not a fan of keeping this -- HACK: fix incorrect require statements. Really not a fan of keeping this,
-- but apparently the regular lua loader also allows this
if idx == 1 then if idx == 1 then
modname = modname:gsub("^%.+", "") modname = modname:gsub("^%.+", "")
basename = modname:gsub("%.", "/") basename = modname:gsub("%.", "/")
idx = modname:find(".", 1, true) idx = modname:find(".", 1, true)
end end
-- get the top-level module name
local topmod = idx and modname:sub(1, idx - 1) or modname local topmod = idx and modname:sub(1, idx - 1) or modname
-- OPTIM: search for a directory first when topmod == modname -- OPTIM: search for a directory first when topmod == modname
@ -245,7 +270,11 @@ function Cache.find(modname, opts)
patterns[p] = "/lua/" .. basename .. pattern patterns[p] = "/lua/" .. basename .. pattern
end end
-- Checks if the given paths contain the top-level module.
-- If so, it tries to find the module path for the given module name.
---@param paths string[] ---@param paths string[]
---@return string? modpath, CacheHash? hash
---@private
local function _find(paths) local function _find(paths)
for _, path in ipairs(paths) do for _, path in ipairs(paths) do
if M.lsmod(path)[topmod] then if M.lsmod(path)[topmod] then
@ -261,9 +290,10 @@ function Cache.find(modname, opts)
end end
end end
---@type string, CacheHash ---@type string?, CacheHash?
local modpath, hash local modpath, hash
-- always check the rtp first
if opts.rtp ~= false then if opts.rtp ~= false then
modpath, hash = _find(Cache._rtp or {}) modpath, hash = _find(Cache._rtp or {})
if not modpath then if not modpath then
@ -273,6 +303,8 @@ function Cache.find(modname, opts)
end end
end end
end end
-- check any additional paths
if (not modpath) and opts.paths then if (not modpath) and opts.paths then
modpath, hash = _find(opts.paths) modpath, hash = _find(opts.paths)
end end
@ -291,6 +323,11 @@ function M.reset(path)
Cache._indexed[Cache.normalize(path)] = nil Cache._indexed[Cache.normalize(path)] = nil
end end
-- Enables the cache:
-- * override loadfile
-- * adds the lua loader
-- * adds the libs loader
-- * remove the Neovim loader
function M.enable() function M.enable()
if M.enabled then if M.enabled then
return return
@ -312,6 +349,9 @@ function M.enable()
end end
end end
-- Disables the cache:
-- * removes the cache loaders
-- * adds the Neovim loader
function M.disable() function M.disable()
if not M.enabled then if not M.enabled then
return return
@ -365,14 +405,7 @@ function M.lsmod(path)
return Cache._indexed[path] return Cache._indexed[path]
end end
---@param modname string -- Debug function that wrapps all loaders and tracks stats
---@param opts? CacheFindOpts
---@return string? modpath
function M.find(modname, opts)
local modpath = Cache.find(modname, opts)
return modpath
end
function M.profile_loaders() function M.profile_loaders()
for l, loader in pairs(package.loaders) do for l, loader in pairs(package.loaders) do
local loc = debug.getinfo(loader, "Sn").source:sub(2) local loc = debug.getinfo(loader, "Sn").source:sub(2)
@ -386,7 +419,9 @@ function M.profile_loaders()
end end
end end
-- Prints all cache stats
function M.inspect() function M.inspect()
---@private
local function ms(nsec) local function ms(nsec)
return math.floor(nsec / 1e6 * 1000 + 0.5) / 1000 .. "ms" return math.floor(nsec / 1e6 * 1000 + 0.5) / 1000 .. "ms"
end end

View File

@ -476,7 +476,7 @@ end
---@param modname string ---@param modname string
function M.loader(modname) function M.loader(modname)
local paths = Util.get_unloaded_rtp(modname) local paths = Util.get_unloaded_rtp(modname)
local modpath, hash = Cache._Cache.find(modname, { rtp = false, paths = paths }) local modpath, hash = Cache.find(modname, { rtp = false, paths = paths })
if modpath then if modpath then
M.auto_load(modname, modpath) M.auto_load(modname, modpath)
local mod = package.loaded[modname] local mod = package.loaded[modname]

View File

@ -250,7 +250,6 @@ function Spec:import(spec)
return return
end end
Cache.indexed_unloaded = false
self.modules[#self.modules + 1] = spec.import self.modules[#self.modules + 1] = spec.import
local imported = 0 local imported = 0