diff --git a/build.cm b/build.cm index f231b39a..176e1cfd 100644 --- a/build.cm +++ b/build.cm @@ -127,7 +127,8 @@ var SALT_MACH = 'mach' // mach bytecode blob var SALT_MCODE = 'mcode' // mcode IR (JSON) var SALT_DEPS = 'deps' // cached cc -MM dependency list var SALT_FAIL = 'fail' // cached compilation failure -var SALT_BMFST = 'bmfst' // stat-based build manifest +var SALT_BMFST = 'bmfst' // stat-based build manifest (object level) +var SALT_BMFST_DL = 'bmfst_dl' // stat-based build manifest (dylib level) function cache_path(content, salt) { return get_build_dir() + '/' + content_hash(content + '\n' + salt) @@ -185,6 +186,54 @@ function bmfst_save(cmd_str, src_path, deps, obj_path) { fd.slurpwrite(mf_path, stone(blob(json.encode(mf)))) } +// Dylib-level stat manifest — keyed on compile cmd + link info + src path. +// All key inputs are available without reading any files. + +function bmfst_dl_key(setup, link_info) { + var parts = [setup.cmd_str, setup.src_path] + push(parts, 'target:' + text(link_info.target)) + push(parts, 'cc:' + text(link_info.cc)) + arrfor(link_info.extra_objects, function(obj) { + if (obj != null) push(parts, 'extra:' + text(obj)) + }) + arrfor(link_info.ldflags, function(flag) { + push(parts, 'ldflag:' + text(flag)) + }) + arrfor(link_info.target_ldflags, function(flag) { + push(parts, 'target_ldflag:' + text(flag)) + }) + return text(parts, '\n') +} + +function bmfst_dl_probe(setup, link_info) { + var mf_path = cache_path(bmfst_dl_key(setup, link_info), SALT_BMFST_DL) + if (!fd.is_file(mf_path)) return null + var mf = json.decode(text(fd.slurp(mf_path))) + if (!mf || !mf.d || !mf.dylib) return null + if (!fd.is_file(mf.dylib)) return null + var ok = true + arrfor(mf.d, function(entry) { + if (!ok) return + var st = memo_stat(entry.p) + if (!st || st.m != entry.m || st.s != entry.s) + ok = false + }) + if (!ok) return null + return mf.dylib +} + +function bmfst_dl_save(setup, link_info, deps, dylib_path) { + var entries = [] + arrfor(deps, function(dep_path) { + var st = memo_stat(dep_path) + if (st) + push(entries, {p: dep_path, m: st.m, s: st.s}) + }) + var mf = {dylib: dylib_path, d: entries} + var mf_path = cache_path(bmfst_dl_key(setup, link_info), SALT_BMFST_DL) + fd.slurpwrite(mf_path, stone(blob(json.encode(mf)))) +} + // ============================================================================ // Dependency scanning helpers // ============================================================================ @@ -525,6 +574,17 @@ Build.build_module_dylib = function(pkg, file, target, opts) { var link_info = {extra_objects: _extra, ldflags: resolved_ldflags, target_ldflags: target_ldflags, target: _target, cc: cc} + // Stat-based dylib manifest — zero file reads on warm cache + var mf_dylib = null + if (!_opts.force) { + mf_dylib = bmfst_dl_probe(setup, link_info) + if (mf_dylib) { + if (_opts.verbose) log.build('[verbose] manifest hit: ' + file) + log.shop('manifest hit ' + file) + return mf_dylib + } + } + // Probe source key — check dylib cache before compiling var probe = probe_source_key(setup, file) var dylib_path = null @@ -547,6 +607,7 @@ Build.build_module_dylib = function(pkg, file, target, opts) { dylib_path = cache_path(dylib_content, SALT_DYLIB) if (!_opts.force && fd.is_file(dylib_path)) { log.shop('cache hit ' + file) + bmfst_dl_save(setup, link_info, probe.deps, dylib_path) return dylib_path } @@ -568,7 +629,10 @@ Build.build_module_dylib = function(pkg, file, target, opts) { if (post_probe && post_probe.full_content) { dylib_content = compute_dylib_content(post_probe.full_content, link_info) dylib_path = cache_path(dylib_content, SALT_DYLIB) - if (fd.is_file(dylib_path)) return dylib_path + if (fd.is_file(dylib_path)) { + bmfst_dl_save(setup, link_info, post_probe.deps, dylib_path) + return dylib_path + } } } } @@ -631,6 +695,13 @@ Build.build_module_dylib = function(pkg, file, target, opts) { return null } + // Save dylib manifest for future stat-based probes + var mf_deps = null + if (fallback_probe && fallback_probe.deps) mf_deps = fallback_probe.deps + if (post_probe && post_probe.deps) mf_deps = post_probe.deps + if (probe && probe.deps) mf_deps = probe.deps + if (mf_deps) bmfst_dl_save(setup, link_info, mf_deps, dylib_path) + return dylib_path } @@ -1093,6 +1164,7 @@ Build.SALT_MCODE = SALT_MCODE Build.SALT_DEPS = SALT_DEPS Build.SALT_FAIL = SALT_FAIL Build.SALT_BMFST = SALT_BMFST +Build.SALT_BMFST_DL = SALT_BMFST_DL Build.cache_path = cache_path Build.manifest_path = manifest_path Build.native_sanitize_flags = native_sanitize_flags