You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

276 lines
10 KiB

#cpufloor_audit.tcl - G-172 audit instrument: disassemble a distributed binary
#(or a pre-disassembled .text excerpt) and report instructions above a declared
#CPU instruction-set floor. The acceptance criterion of G-172 ("a flagless build
#of each distributed-artifact recipe emits zero instructions above the declared
#floor") is measurable by THIS tool rather than by a promise about build habits.
#
#The defect class (see goals/G-172-distributed-binary-cpu-floor.md Context): zig
#auto-vectorises ReleaseFast codegen to whatever the build host's CPU happens
#to expose when a recipe resolves cpu_model .determined_by_arch_os by NATIVE
#detection. On a Zen 5 host that is AVX-512 - real EVEX-encoded zmm-operand
#instructions at the front of .text. Tcl has no runtime CPU dispatch, so on a
#non-AVX-512 CPU the first one raises #UD and the process dies with
#STATUS_ILLEGAL_INSTRUCTION (0xC000001D) before writing a byte. The 2026-08-06
#regression shipped the entire published tclsh9.0.5 family (2756 zmm sites each)
#and the build-path tools (punkzip 1724, punkres 1423) - the bundled fixtures
#are real excerpts of that codegen class.
#
#Detection is by AVX REGISTER WIDTH, the same signal the Context measurements
#used: a `zmm[0-9]+` operand is AVX-512 (x86-64 v4), a `ymm[0-9]+` operand is
#AVX2 (x86-64 v3). Size specifiers (zmmword/ymmword/xmmword) never match - they
#lack the trailing digit. SSE4.2/POPCNT (the v2-only delta over baseline) is
#not detectable by width; baseline and v2 therefore share the same register
#ceiling (no AVX) and audit identically. The regression is AVX-width, so this
#is the honest instrument for it; a future finer-grained floor (per-instruction
#mnemonic classification) is a follow-on, not required by the acceptance.
#
#Plain tclsh, no dependencies. Exit 0 = no instructions above floor (PASS);
#exit 1 = out-of-floor instructions found (FAIL) or a selftest failure.
#
#Usage:
# tclsh cpufloor_audit.tcl ?-floor baseline|v2|v3|v4? <binary>
# Disassemble <binary>'s .text with a located objdump and audit it.
# llvm-objdump is preferred (Intel syntax), GNU objdump (-M intel) is the
# fallback. No disassembler on PATH is a hard error (install llvm-objdump,
# or pipe a pre-disassembled excerpt in with -from-dis).
# tclsh cpufloor_audit.tcl -from-dis ?-floor <f>? <dis-text-file>
# Audit a pre-disassembled .text excerpt (objdump -d output) directly -
# no disassembler needed. The bundled fixtures are this shape.
# tclsh cpufloor_audit.tcl -selftest
# Run the bundled fixtures (scriptlib/developer/cpufloor_fixtures/) through
# every floor and check the verdicts - the regression-as-fixture contract.
#
#Floor defaults to baseline (the project's chosen floor for the x86_64 family -
#see G-172 Approach 2: a Tcl interpreter's codegen delta against v2/v3 is
#negligible against the cost of the default runtime failing to start).
package require Tcl 8.6
proc scriptdir {} {
global argv0
return [file dirname [file normalize $argv0]]
}
proc floor_forbidden {floor} {
switch -- $floor {
baseline { return [dict create zmm 1 ymm 1] }
v2 { return [dict create zmm 1 ymm 1] }
v3 { return [dict create zmm 1 ymm 0] }
v4 { return [dict create zmm 0 ymm 0] }
default { error "unknown floor '$floor' (expected baseline, v2, v3 or v4)" }
}
}
#is <line> an instruction line (addr: hexbytes... mnemonic...)?
proc is_insn_line {line} {
return [regexp {^\s*[0-9a-fA-F]+:\s+([0-9a-fA-F]{2}\s)+} $line]
}
#extract the mnemonic of an instruction line (first token after the hex-byte run)
proc insn_mnemonic {line} {
if {![regexp {^\s*[0-9a-fA-F]+:\s+(.+)$} $line -> rest]} { return "" }
set rest [regsub {^([0-9a-fA-F]{2}\s+)+} $rest ""]
set rest [regsub {^\s+} $rest ""]
set mnem [lindex [split $rest \t] 0]
return [lindex [split $mnem " "] 0]
}
#audit a list of disassembly lines against a floor.
#returns dict: total zmm_sites ymm_sites out_of_floor first_rva sample_mnemonics
proc audit_lines {lines floor} {
set forbidden [floor_forbidden $floor]
set fz [dict get $forbidden zmm]
set fy [dict get $forbidden ymm]
set total 0
set zmm 0
set ymm 0
set out 0
set first_rva ""
set mnemonics {}
foreach line $lines {
if {![is_insn_line $line]} continue
incr total
set line_zmm [expr {[regexp {zmm[0-9]+} $line] ? 1 : 0}]
set line_ymm [expr {[regexp {ymm[0-9]+} $line] ? 1 : 0}]
if {$line_zmm} { incr zmm }
if {$line_ymm} { incr ymm }
set line_out 0
if {$line_zmm && $fz} { set line_out 1 }
if {$line_ymm && $fy} { set line_out 1 }
if {!$line_out} continue
incr out
if {$first_rva eq ""} {
regexp {^\s*([0-9a-fA-F]+):} $line -> first_rva
}
set mnem [insn_mnemonic $line]
if {$mnem ne "" && $mnem ni $mnemonics && [llength $mnemonics] < 12} {
lappend mnemonics $mnem
}
}
return [dict create total $total zmm_sites $zmm ymm_sites $ymm \
out_of_floor $out first_rva $first_rva sample_mnemonics $mnemonics]
}
#locate a disassembler and return a command prefix that disassembles .text in
#Intel syntax. Empty string = none found.
proc find_disassembler {} {
foreach {cmd} {
{llvm-objdump -d --section=.text --x86-asm-syntax=intel}
{objdump -d --section=.text -M intel}
} {
set exe [auto_execok [lindex $cmd 0]]
if {$exe ne ""} {
return [concat [list $exe] [lrange $cmd 1 end]]
}
}
return ""
}
proc read_lines {path} {
set f [open $path r]
fconfigure $f -translation binary
set data [read $f]
close $f
#normalise CRLF -> LF so the parser sees one line model
return [split [string map [list \r\n \n \r \n] $data] \n]
}
proc report {path floor res} {
set out [dict get $res out_of_floor]
set zmm [dict get $res zmm_sites]
set ymm [dict get $res ymm_sites]
set total [dict get $res total]
puts "cpufloor_audit: $path"
puts " floor: $floor"
puts " .text insns: $total"
puts " zmm sites (v4): $zmm"
puts " ymm sites (v3): $ymm"
puts " out-of-floor: $out"
if {$out > 0} {
puts " first out-of-floor RVA: [dict get $res first_rva]"
set ms [dict get $res sample_mnemonics]
if {[llength $ms]} {
puts " sample out-of-floor mnemonics: [join $ms {, }]"
}
puts " VERDICT: FAIL - instructions above the declared floor are present"
} else {
puts " VERDICT: PASS - no instructions above the declared floor"
}
}
proc usage {} {
puts stderr "usage: tclsh cpufloor_audit.tcl ?-floor baseline|v2|v3|v4? <binary>"
puts stderr " tclsh cpufloor_audit.tcl -from-dis ?-floor <f>? <dis-text-file>"
puts stderr " tclsh cpufloor_audit.tcl -selftest"
}
proc selftest {} {
set d [file join [scriptdir] cpufloor_fixtures]
set cases {
{avx512.dis.txt baseline 1}
{avx512.dis.txt v2 1}
{avx512.dis.txt v3 1}
{avx512.dis.txt v4 0}
{avx2.dis.txt baseline 1}
{avx2.dis.txt v2 1}
{avx2.dis.txt v3 0}
{avx2.dis.txt v4 0}
{clean.dis.txt baseline 0}
{clean.dis.txt v2 0}
{clean.dis.txt v3 0}
{clean.dis.txt v4 0}
}
set fail 0
foreach case $cases {
lassign $case fixture floor expect_fail
set p [file join $d $fixture]
if {![file exists $p]} {
puts "SELFTEST FAIL: fixture missing: $p"
incr fail
continue
}
set res [audit_lines [read_lines $p] $floor]
set got [dict get $res out_of_floor]
set got_fail [expr {$got > 0 ? 1 : 0}]
set zmm [dict get $res zmm_sites]
set ymm [dict get $res ymm_sites]
set status OK
if {$got_fail != $expect_fail} {
set status "MISMATCH (expected [expr {$expect_fail ? {FAIL} : {PASS}}])"
incr fail
}
puts [format "SELFTEST %-16s floor=%-9s zmm=%-4d ymm=%-4d out=%-4d %s" \
$fixture $floor $zmm $ymm $got $status]
}
if {$fail} {
puts "SELFTEST: $fail case(s) failed"
return 1
}
puts "SELFTEST: all cases pass"
return 0
}
#--- argv parsing ---
set floor baseline
set from_dis 0
set selftest_mode 0
set positional {}
foreach a $argv {
switch -- $a {
-floor { set expecting_floor 1 }
-from-dis { set from_dis 1 }
-selftest { set selftest_mode 1 }
-help - --help - -h {
usage; exit 0
}
default {
if {[info exists expecting_floor]} {
set floor $a
unset expecting_floor
} else {
lappend positional $a
}
}
}
}
if {[info exists expecting_floor]} {
usage; exit 1
}
if {$selftest_mode} {
exit [selftest]
}
if {[llength $positional] == 0} {
usage; exit 1
}
set target [lindex $positional 0]
if {![file exists $target]} {
puts stderr "cpufloor_audit: no such file: $target"
exit 1
}
if {$from_dis} {
set lines [read_lines $target]
} else {
set dis [find_disassembler]
if {$dis eq ""} {
puts stderr "cpufloor_audit: no disassembler found on PATH (tried llvm-objdump, objdump)."
puts stderr " install llvm-objdump, or pass a pre-disassembled excerpt with -from-dis."
exit 1
}
set cmd [concat $dis [list $target]]
if {[catch {exec {*}$cmd 2>@1} disout]} {
#objdump returns nonzero on some warnings but still emits disassembly;
#only treat as fatal if there is no instruction output at all
if {![regexp -inline {[0-9a-fA-F]+:} $disout]} {
puts stderr "cpufloor_audit: disassembler failed: $disout"
exit 1
}
}
set lines [split [string map [list \r\n \n \r \n] $disout] \n]
}
set res [audit_lines $lines $floor]
report $target $floor $res
exit [expr {[dict get $res out_of_floor] > 0 ? 1 : 0}]