parallel assembly

This commit is contained in:
2026-02-17 10:57:50 -06:00
parent 8c408a4b81
commit 5fcf765c8d
3 changed files with 126 additions and 59 deletions

113
build.cm
View File

@@ -467,6 +467,64 @@ Build.build_static = function(packages, target, output, buildtype) {
// Native .cm compilation (source → mcode → QBE IL → .o → .dylib)
// ============================================================================
// Batched native compilation: split functions into batches, run QBE on each,
// assemble in parallel, return array of .o paths.
// il_parts: {data: text, functions: [text, ...]}
// cc: C compiler path
// tmp_prefix: prefix for temp files (e.g. /tmp/cell_native_<hash>)
function compile_native_batched(il_parts, cc, tmp_prefix) {
var nfuncs = length(il_parts.functions)
var nbatch = 8
var o_paths = []
var s_paths = []
var asm_cmds = []
var batch_fns = null
var batch_il = null
var asm_text = null
var s_path = null
var o_path = null
var end = 0
var bi = 0
var fi = 0
var ai = 0
var rc = null
var parallel_cmd = null
if (nfuncs < nbatch) nbatch = nfuncs
if (nbatch < 1) nbatch = 1
// Generate .s files: run QBE on each batch
while (bi < nbatch) {
batch_fns = []
end = nfuncs * (bi + 1) / nbatch
while (fi < end) {
batch_fns[] = il_parts.functions[fi]
fi = fi + 1
}
batch_il = il_parts.data + "\n\n" + text(batch_fns, "\n")
asm_text = os.qbe(batch_il)
s_path = tmp_prefix + '_b' + text(bi) + '.s'
o_path = tmp_prefix + '_b' + text(bi) + '.o'
fd.slurpwrite(s_path, stone(blob(asm_text)))
s_paths[] = s_path
o_paths[] = o_path
bi = bi + 1
}
// Assemble all batches in parallel
while (ai < length(s_paths)) {
asm_cmds[] = cc + ' -c ' + s_paths[ai] + ' -o ' + o_paths[ai]
ai = ai + 1
}
parallel_cmd = text(asm_cmds, ' & ') + ' & wait'
rc = os.system(parallel_cmd)
if (rc != 0) {
print('Parallel assembly failed'); disrupt
}
return o_paths
}
// Post-process QBE IL: insert dead labels after ret/jmp (QBE requirement)
function qbe_insert_dead_labels(il_text) {
var lines = array(il_text, "\n")
@@ -536,10 +594,8 @@ Build.compile_native = function(src_path, target, buildtype, pkg) {
if (pkg) {
sym_name = shop.c_symbol_for_file(pkg, fd.basename(src_path))
}
var il = qbe_emit(optimized, qbe_macros, sym_name)
// Step 3: Post-process (insert dead labels)
il = qbe_insert_dead_labels(il)
var il_parts = qbe_emit(optimized, qbe_macros, sym_name)
var il = il_parts.data + "\n\n" + text(il_parts.functions, "\n")
// Content hash for cache key
var hash = content_hash(src + '\n' + _target + '\nnative')
@@ -550,22 +606,14 @@ Build.compile_native = function(src_path, target, buildtype, pkg) {
if (fd.is_file(dylib_path))
return dylib_path
// Step 4: QBE compile IR to assembly (in-process)
// Compile and assemble via batched parallel pipeline
var tmp = '/tmp/cell_native_' + hash
var s_path = tmp + '.s'
var o_path = tmp + '.o'
var rt_o_path = '/tmp/cell_qbe_rt.o'
var asm_text = os.qbe(il)
fd.slurpwrite(s_path, stone(blob(asm_text)))
var o_paths = compile_native_batched(il_parts, cc, tmp)
// Step 5: Assemble
var rc = os.system(cc + ' -c ' + s_path + ' -o ' + o_path)
if (rc != 0) {
print('Assembly failed for: ' + src_path); disrupt
}
// Step 7: Compile QBE runtime stubs if needed
// Compile QBE runtime stubs if needed
var rc = null
if (!fd.is_file(rt_o_path)) {
qbe_rt_path = shop.get_package_dir('core') + '/qbe_rt.c'
rc = os.system(cc + ' -c ' + qbe_rt_path + ' -o ' + rt_o_path + ' -fPIC')
@@ -574,14 +622,19 @@ Build.compile_native = function(src_path, target, buildtype, pkg) {
}
}
// Step 8: Link dylib
// Link dylib
var link_cmd = cc + ' -shared -fPIC'
if (tc.system == 'darwin') {
link_cmd = link_cmd + ' -undefined dynamic_lookup'
} else if (tc.system == 'linux') {
link_cmd = link_cmd + ' -Wl,--allow-shlib-undefined'
}
link_cmd = link_cmd + ' ' + o_path + ' ' + rt_o_path + ' -o ' + dylib_path
var oi = 0
while (oi < length(o_paths)) {
link_cmd = link_cmd + ' ' + o_paths[oi]
oi = oi + 1
}
link_cmd = link_cmd + ' ' + rt_o_path + ' -o ' + dylib_path
rc = os.system(link_cmd)
if (rc != 0) {
@@ -625,8 +678,7 @@ Build.compile_native_ir = function(optimized, src_path, opts) {
if (pkg) {
sym_name = shop.c_symbol_for_file(pkg, fd.basename(src_path))
}
var il = qbe_emit(optimized, qbe_macros, sym_name)
il = qbe_insert_dead_labels(il)
var il_parts = qbe_emit(optimized, qbe_macros, sym_name)
var src = text(fd.slurp(src_path))
var hash = content_hash(src + '\n' + _target + '\nnative')
@@ -637,19 +689,14 @@ Build.compile_native_ir = function(optimized, src_path, opts) {
if (fd.is_file(dylib_path))
return dylib_path
// Compile and assemble via batched parallel pipeline
var tmp = '/tmp/cell_native_' + hash
var s_path = tmp + '.s'
var o_path = tmp + '.o'
var rt_o_path = '/tmp/cell_qbe_rt.o'
var asm_text = os.qbe(il)
fd.slurpwrite(s_path, stone(blob(asm_text)))
var rc = os.system(cc + ' -c ' + s_path + ' -o ' + o_path)
if (rc != 0) {
print('Assembly failed for: ' + src_path); disrupt
}
var o_paths = compile_native_batched(il_parts, cc, tmp)
// Compile QBE runtime stubs if needed
var rc = null
if (!fd.is_file(rt_o_path)) {
qbe_rt_path = shop.get_package_dir('core') + '/qbe_rt.c'
rc = os.system(cc + ' -c ' + qbe_rt_path + ' -o ' + rt_o_path + ' -fPIC')
@@ -658,13 +705,19 @@ Build.compile_native_ir = function(optimized, src_path, opts) {
}
}
// Link dylib
var link_cmd = cc + ' -shared -fPIC'
if (tc.system == 'darwin') {
link_cmd = link_cmd + ' -undefined dynamic_lookup'
} else if (tc.system == 'linux') {
link_cmd = link_cmd + ' -Wl,--allow-shlib-undefined'
}
link_cmd = link_cmd + ' ' + o_path + ' ' + rt_o_path + ' -o ' + dylib_path
var oi = 0
while (oi < length(o_paths)) {
link_cmd = link_cmd + ' ' + o_paths[oi]
oi = oi + 1
}
link_cmd = link_cmd + ' ' + rt_o_path + ' -o ' + dylib_path
rc = os.system(link_cmd)
if (rc != 0) {