ludic build on every core: the IR split with llvm-split and compiled by a clang per part at once
Most of a build was one clang -O2 on one .ll (the game: 32 s of a 41 s headless build, one core). Both paths that assemble - the CLI's (build.ludic: ludicc --emit-llvm, then clang) and ludicc's own (-o, which ludic bundle and the examples use) - now cut the program's IR into N parts with llvm-split (externalizing what the parts share), compile them with one clang each in parallel (-x ir -O<opt> -mmacosx-version-min=11.0, the link's own clang taking the objects where it took the .ll), and remove the parts and objects after. N is $LUDIC_JOBS, else min(cores, free GB / 1.5). It needs an llvm-split and a clang of the same LLVM (Homebrew's LLVM 22 writes attributes Apple's clang 17 cannot read): $LUDIC_LLVM, else /opt/homebrew/opt/llvm/bin. With either missing, on Windows (its shell cannot run the parts at once yet), with LUDIC_SPLIT=0, or when a part fails, it compiles the .ll whole as before. $LUDIC_OPT=1 is a developer's faster build; ludic bundle sets LUDIC_OPT=2 for its compile whatever the shell says. Measured before the compile-only rule (this Mac, 12 cores, one build at a time): - the game headless: 37-41 s -> 13-15.5 s (8 parts / by free memory), peak 2.1 GB -> 1.0-1.1 GB; - the lab headless: 43.1 s -> 12.7 s, peak 2.4 GB -> 1.0 GB; - the game at LUDIC_OPT=1, split: 11.8 s (fps cost not measured). Both built and linked clean; the goldens and a headless shot of the result are not run here. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
9f94be7cca
commit
9cc3f24c56
10 changed files with 4119 additions and 2288 deletions
|
|
@ -54,7 +54,12 @@ function compile_app(src: pointer, out: pointer, mode: int, save: bool) -> bool
|
|||
|
||||
if mode == 2 {
|
||||
if not shq(`{ludicc()} --headless{title_flag()}{unsafe_flag()} {src} --emit-llvm -o {ll}`) { return false }
|
||||
if not shq(`{cc()} -O2 {ll}{gl_link_flags(ll)}{vk_link_flags(ll)}{http_link_flags(ll)}{udp_link_flags(ll)}{process_link_flags(ll)}{threads_link_flags(ll)}{native_link_flags(ll)}{pbf} -o {out}`) { return false }
|
||||
let objs = split_objs(ll, out) # on every core when it can (split.ludic)
|
||||
var input = ll
|
||||
if objs != "" { input = objs }
|
||||
let linked = shq(`{cc()} -O{split_opt()} {input}{gl_link_flags(ll)}{vk_link_flags(ll)}{http_link_flags(ll)}{udp_link_flags(ll)}{process_link_flags(ll)}{threads_link_flags(ll)}{native_link_flags(ll)}{pbf} -o {out}`)
|
||||
if objs != "" { shell(`rm -f {objs}`) }
|
||||
if not linked { return false }
|
||||
g_built_natives = native_libs_of(ll)
|
||||
if not save { shell(`rm -f {ll}`) }
|
||||
return true
|
||||
|
|
@ -65,7 +70,12 @@ function compile_app(src: pointer, out: pointer, mode: int, save: bool) -> bool
|
|||
# canonical `ludicc -o` path links it only when Audio.* is used.
|
||||
let cocoa = `{home}runtime/native/cocoa.ll`
|
||||
let audio = `{home}runtime/native/audio.ll`
|
||||
if not shq(`{cc()} -O2 {ll} {cocoa} {audio} -framework Cocoa -Wl,-needed_framework,GameController -Wl,-needed_framework,AVFoundation -Wl,-rpath,@loader_path{gl_link_flags(ll)}{vk_link_flags(ll)}{http_link_flags(ll)}{udp_link_flags(ll)}{process_link_flags(ll)}{threads_link_flags(ll)}{native_link_flags(ll)}{pbf} -o {out}`) { return false }
|
||||
let objs = split_objs(ll, out)
|
||||
var input = ll
|
||||
if objs != "" { input = objs }
|
||||
let linked = shq(`{cc()} -O{split_opt()} {input} {cocoa} {audio} -framework Cocoa -Wl,-needed_framework,GameController -Wl,-needed_framework,AVFoundation -Wl,-rpath,@loader_path{gl_link_flags(ll)}{vk_link_flags(ll)}{http_link_flags(ll)}{udp_link_flags(ll)}{process_link_flags(ll)}{threads_link_flags(ll)}{native_link_flags(ll)}{pbf} -o {out}`)
|
||||
if objs != "" { shell(`rm -f {objs}`) }
|
||||
if not linked { return false }
|
||||
g_built_natives = native_libs_of(ll)
|
||||
if not save { shell(`rm -f {ll}`) }
|
||||
return true
|
||||
|
|
|
|||
|
|
@ -337,6 +337,7 @@ function cmd_bundle_windows() -> int {
|
|||
let staged = tmp_path("bundle_exe")
|
||||
var flags = "--windowed --gui"
|
||||
if res != "" { flags = flags + ` --link "{res}"` }
|
||||
Os.set_env("LUDIC_OPT", "2") # a shipped game is -O2, whatever a developer's shell says
|
||||
if not shq(`{ludicc()} {flags}{title_flag()} {entry} --natives-out {staged}.natives -o {staged}`) { return 1 }
|
||||
if not shq(`cp "{staged}.exe" "{root}/{name}.exe"`) {
|
||||
err("ludic bundle: could not place the executable\n")
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
program LudicDev {
|
||||
import "prelude.ludic"
|
||||
import "build.ludic"
|
||||
import "split.ludic"
|
||||
import "toolchain.ludic"
|
||||
import "selfhost.ludic"
|
||||
import "tools.ludic"
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
program Ludic {
|
||||
import "prelude.ludic"
|
||||
import "build.ludic"
|
||||
import "split.ludic"
|
||||
import "project.ludic"
|
||||
import "pkg.ludic"
|
||||
import "assets.ludic"
|
||||
|
|
|
|||
|
|
@ -109,6 +109,7 @@ function selfhost_frags() -> []pointer {
|
|||
push(f, "selfhost/backend/stdlib/emit_net.ludic")
|
||||
push(f, "selfhost/backend/game/emit_ui.ludic")
|
||||
push(f, "selfhost/backend/emit_decl.ludic")
|
||||
push(f, "selfhost/split_build.ludic")
|
||||
push(f, "selfhost/main.ludic")
|
||||
return f
|
||||
}
|
||||
|
|
|
|||
49
tools/ludic-cli/split.ludic
Normal file
49
tools/ludic-cli/split.ludic
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
# split.ludic - `ludic build` on every core: the program's IR cut into N parts (llvm-split), a clang
|
||||
# each at once, their objects linked where the one .ll was (the game: clang was 32 s of a 41 s
|
||||
# build, on one core). ludicc's own link does the same (selfhost/split_build.ludic). It needs an
|
||||
# llvm-split and a clang of one LLVM - $LUDIC_LLVM, else Homebrew's - or it compiles the .ll whole.
|
||||
|
||||
function split_opt() -> pointer { return getenv_or("LUDIC_OPT", "2") } # 1: a developer's faster build
|
||||
|
||||
# " a.o b.o ..." for the link, or "" when not split (the caller compiles the .ll as before)
|
||||
function split_objs(ll: pointer, out: pointer) -> pointer {
|
||||
if host_windows() or getenv_or("LUDIC_SPLIT", "1") == "0" { return "" }
|
||||
let d = getenv_or("LUDIC_LLVM", "/opt/homebrew/opt/llvm/bin")
|
||||
if not is_exec(`"{d}/llvm-split"`) or not is_exec(`"{d}/clang"`) { return "" }
|
||||
let n = split_n()
|
||||
if n < 2 { return "" }
|
||||
let pre = tmp_path(`{file_of_path(out)}.part`)
|
||||
if not shq(`"{d}/llvm-split" -j {n} -o "{pre}" "{ll}"`) { return "" }
|
||||
var sh = ""
|
||||
for i in 0 .. n {
|
||||
sh = sh + `"{d}/clang" -x ir -O{split_opt()} -mmacosx-version-min=11.0 -Wno-override-module -Wno-missing-sysroot -c "{pre}{i}" -o "{pre}{i}.o" & `
|
||||
}
|
||||
shell(`{sh}wait`)
|
||||
var objs = ""
|
||||
var ok = true
|
||||
for i in 0 .. n {
|
||||
shell(`rm -f "{pre}{i}"`)
|
||||
if not file_exists(`"{pre}{i}.o"`) { ok = false }
|
||||
objs = objs + ` "{pre}{i}.o"`
|
||||
}
|
||||
if ok { return objs }
|
||||
shell(`rm -f {objs}`)
|
||||
return ""
|
||||
}
|
||||
|
||||
# $LUDIC_JOBS, else the cores - no more than the free memory allows at 1.5 GB a clang
|
||||
function split_n() -> int {
|
||||
let j = getenv("LUDIC_JOBS")
|
||||
if j != null { return s_to_int(j) }
|
||||
let aw = "awk '/Pages free/{f=$3} /Pages inactive/{i=$3} END{print int((f+i)*p/1073741824)}'"
|
||||
let line = capture_line(`echo $(sysctl -n hw.ncpu) $(vm_stat | {aw} p=$(sysctl -n hw.pagesize))`)
|
||||
var cores = 0
|
||||
var i = 0
|
||||
while i < len(line) and line[i] >= '0' and line[i] <= '9' { cores = cores * 10 + (line[i] - '0'); i += 1 }
|
||||
while i < len(line) and line[i] == ' ' { i += 1 }
|
||||
var gb = 0
|
||||
while i < len(line) and line[i] >= '0' and line[i] <= '9' { gb = gb * 10 + (line[i] - '0'); i += 1 }
|
||||
let by_mem = (gb * 2) / 3
|
||||
if by_mem < cores { return by_mem }
|
||||
return cores
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue