packages feed

crackNum 3.17 → 3.18

raw patch · 7 files changed

+816/−39 lines, 7 files

Files

CHANGES.md view
@@ -1,7 +1,36 @@ * Hackage: <http://hackage.haskell.org/package/crackNum> * GitHub:  <http://github.com/LeventErkok/crackNum/> -* Latest Hackage released version: 3.17, 2026-08-10+* Latest Hackage released version: 3.18, 2026-08-13++### Version 3.18, 2026-08-13++  * Add a Tcl/Tk GUI (`GUI/tclGUI/crackNum.tcl`) that works on Linux and macOS.+    When `--gui` is used on Linux, `crackNum` now launches this interface+    (requires `wish`) instead of erroring out.++  * The Tcl/Tk GUI script is now a cabal data-file, so it is installed along+    with the binary: `cabal install crackNum` is enough for `crackNum --gui` to+    work on Linux, with no PATH setup. To run a different copy of the script,+    set `CRACKNUM_TCL`, or put it on your PATH as `crackNum.tcl`.++  * Both GUIs now expose the number of lanes (`-l`), so multi-lane decoding is+    reachable from the graphical interface, and `--gui -l4 ...` is honored+    rather than silently dropped.++  * Both front-ends now live under `GUI/`: the macOS app moved from `gui/` to+    `GUI/swiftGUI/`, and the Tcl/Tk script to `GUI/tclGUI/`.++  * Encoding a NaN now always displays the canonical quiet-NaN pattern (sign 0,+    all-ones exponent, leading significand bit set; `0x7FC00000` for a single).+    SMTLib's floating-point sort has a single NaN value, so the solver returns an+    abstract NaN and the concrete bit-pattern shown was whatever the model+    materialized -- which could differ between solver/library versions. The+    E4M3 path already pinned its NaN this way; the rest now do too.++  * Exponent/significand sizes of 1 bit are accepted by `-f`, but the solver+    requires at least 2 of each. This now produces a regular error message+    instead of an uncaught exception with a backtrace.  ### Version 3.17, 2026-08-10 
+ GUI/tclGUI/README.md view
@@ -0,0 +1,91 @@+# CrackNum Tcl/Tk GUI++A cross-platform GUI for [crackNum](https://github.com/LeventErkok/crackNum), written in Tcl/Tk.+Works on Linux and macOS anywhere `wish` (Tk 8.6+) is available.++## Requirements++- `crackNum` on your PATH+- `z3` on your PATH+- `wish` (Tk 8.6+)++On NixOS / Nix:++```bash+nix profile install nixpkgs#tk+```++On Debian/Ubuntu:++```bash+sudo apt install tk+```++On RHEL/Fedora/Rocky:++```bash+sudo dnf install tk+```++## Installation++Nothing to do: this script is a cabal data-file, so `cabal install crackNum`+puts it on disk next to the binary, and `crackNum --gui` finds it there.++If you do not have it — say you only have the binary, or you moved it — get the+sources with either of:++```bash+cabal get crackNum+git clone http://github.com/LeventErkok/crackNum.git+```++### Running a different copy++`crackNum` looks for the script in three places, first match wins:++| Order | Location                             | Use it for                        |+|-------|--------------------------------------|-----------------------------------|+| 1     | `$CRACKNUM_TCL`                      | pointing at an explicit file      |+| 2     | `crackNum.tcl` on your PATH          | shadowing with a checkout         |+| 3     | the copy installed with the package  | the normal case; nothing to set   |++So to test a modified script:++```bash+export CRACKNUM_TCL=/path/to/crackNum/GUI/tclGUI/crackNum.tcl+```++or put its directory on your PATH (the script must be executable for this route):++```bash+export PATH=/path/to/crackNum/GUI/tclGUI:$PATH+```++When working inside a checkout, `cabal run crackNum -- --gui` also works: cabal+sets `crackNum_datadir` so the in-tree copy is used.++## Usage++Launch via the `crackNum` binary:++```bash+crackNum --gui+crackNum --gui -fsp 2.5+crackNum --gui -w32 0xDEADBEEF+```++Or directly with `wish`:++```bash+wish crackNum.tcl+wish crackNum.tcl -fsp 2.5+```++## Keyboard shortcuts++| Key      | Action          |+|----------|-----------------|+| Ctrl+W   | Close window    |+| Ctrl+Q   | Quit            |+| Return   | Crack the value |
+ GUI/tclGUI/crackNum.tcl view
@@ -0,0 +1,524 @@+#!/usr/bin/env wish+# CrackNum GUI — Tcl/Tk front-end for the crackNum command-line tool.+# Works on Linux and macOS. Requires wish (Tk 8.5+) and crackNum on PATH.++# ---------------------------------------------------------------------------+# Tool discovery+# ---------------------------------------------------------------------------++proc locate {name} {+    set path [split [expr {+        [info exists ::env(PATH)] ? $::env(PATH) : "/usr/bin:/bin"+    }] :]+    foreach dir $path {+        set candidate [file join $dir $name]+        if {[file executable $candidate]} { return $candidate }+    }+    return ""+}++set CRACKNUM [locate crackNum]+set Z3       [locate z3]++# ---------------------------------------------------------------------------+# Format table+# ---------------------------------------------------------------------------+# Each entry: {id label flag_kind flag_arg}+#   flag_kind = fixed | customFloat | word | customWord | int | customInt+#   flag_arg  = the flag suffix for "fixed", or bit-count for "word"/"int"++set FORMAT_SECTIONS {+    {"Float" {+        {ffp4  "FP4 (E2M1)"  fixed    fp4}+        {fe4m3 "FP8 (E4M3)"  fixed    e4m3}+        {fe5m2 "FP8 (E5M2)"  fixed    e5m2}+        {fhp   "Half"        fixed    hp}+        {fbp   "Brain"       fixed    bp}+        {fsp   "Single"      fixed    sp}+        {fdp   "Double"      fixed    dp}+        {fcs   "Custom"      customFloat {}}+    }}+    {"Word (Unsigned)" {+        {w8   "8-bit"   word    8}+        {w16  "16-bit"  word   16}+        {w32  "32-bit"  word   32}+        {w64  "64-bit"  word   64}+        {wcs  "Custom"  customWord {}}+    }}+    {"Integer (Signed)" {+        {i8   "8-bit"   int    8}+        {i16  "16-bit"  int   16}+        {i32  "32-bit"  int   32}+        {i64  "64-bit"  int   64}+        {ics  "Custom"  customInt {}}+    }}+}++set ROUNDING_MODES {RNE RNA RTP RTN RTZ}+array set ROUNDING_LABELS {+    RNE "RNE (Nearest, ties to even)"+    RNA "RNA (Nearest, ties to away)"+    RTP "RTP (Toward +∞)"+    RTN "RTN (Toward -∞)"+    RTZ "RTZ (Toward 0)"+}++# ---------------------------------------------------------------------------+# State+# ---------------------------------------------------------------------------+set state(selection) ""   ;# selected format id+set state(value)     ""+set state(rounding)  "RNE"+set state(lanes)     1+set state(bitWidth)  64+set state(expWidth)  11+set state(fontSize)  11++set WELCOME {Enter a value above, then pick a format on the left to crack it.++You can:+  - ENCODE: from a mathematical value to its internal representation+  - DECODE: from an internal representation to its mathematical value++Encoding:+  - Enter a decimal value (2.5, -4.1e5) or hex float (0x2.4p3).+  - You can pass NaN, Inf, -0, -Inf for special values.+  - For floats, pick a rounding mode.+  - Input must NOT start with 0x, 0b, or N'h (else we decode instead).++Decoding:+  - Use hex (0x), binary (0b), or Verilog (N'h) notation.+  - You may use _, - or space as separators for readability.+  - Verilog input longer than the format is decoded as SIMD lanes.}++# ---------------------------------------------------------------------------+# Build the precision flag from the selected format+# ---------------------------------------------------------------------------+proc precisionFlag {} {+    global state FORMAT_SECTIONS++    set sel $state(selection)+    if {$sel eq ""} { return "" }++    foreach section $FORMAT_SECTIONS {+        foreach fmt [lindex $section 1] {+            lassign $fmt id label kind arg+            if {$id ne $sel} continue++            switch $kind {+                fixed       { return "-f$arg" }+                word        { return "-w$arg" }+                int         { return "-i$arg" }+                customWord  { return "-w$state(bitWidth)" }+                customInt   { return "-i$state(bitWidth)" }+                customFloat {+                    # Only check that the widths describe a well-formed layout; crackNum+                    # itself owns the remaining limits (and reports solver restrictions+                    # readably).+                    set bw $state(bitWidth)+                    set ew $state(expWidth)+                    set sig [expr {$bw - $ew - 1}]+                    if {$ew < 1 || $sig < 0} {+                        return [list invalid \+"Invalid custom FP format:+  Total width: $bw+    Sign       :    1+    Exponent   : [format %4d $ew]+    Significand: [format %4d $sig] (Total = Sign + Exponent + Significand)++Exponent must be at least 1 bit, and the total width must leave room for it and the sign."]+                    }+                    return "-f${ew}+[expr {$bw - $ew}]"+                }+            }+        }+    }+    return ""+}++# ---------------------------------------------------------------------------+# Run crackNum and return output text+# ---------------------------------------------------------------------------+proc runCrackNum {} {+    global state CRACKNUM Z3++    if {$CRACKNUM eq ""} {+        return "crackNum: Cannot locate the 'crackNum' binary on your PATH.\n\nMake sure it is installed and reachable (e.g. `which crackNum` works in your terminal)."+    }+    if {$Z3 eq ""} {+        return "crackNum: Cannot locate the 'z3' binary on your PATH.\n\nMake sure it is installed and reachable (e.g. `which z3` works in your terminal)."+    }++    set flagResult [precisionFlag]+    if {$flagResult eq ""} { return "" }++    if {[lindex $flagResult 0] eq "invalid"} {+        return [lindex $flagResult 1]+    }++    set flag $flagResult+    set rm   "-r$state(rounding)"+    set val  [expr {$state(value) eq "" ? "0" : $state(value)}]++    # Pass SBV_Z3 so crackNum finds z3 even when PATH is minimal.+    set savedZ3 [expr {[info exists ::env(SBV_Z3)] ? $::env(SBV_Z3) : ""}]+    set ::env(SBV_Z3) $Z3++    set cmd [list $CRACKNUM $flag $rm]+    # Only pass -l when it's actually multi-lane: giving -l1 explicitly would+    # suppress crackNum's lane inference for Verilog (N'h) input.+    if {[string is integer -strict $state(lanes)] && $state(lanes) > 1} {+        lappend cmd -l$state(lanes)+    }+    lappend cmd -- $val++    # 2>@1 folds stderr into the captured result, so errors show up in the pane.+    set rc [catch {exec {*}$cmd 2>@1} output]++    if {$savedZ3 eq ""} { unset -nocomplain ::env(SBV_Z3) } \+    else                { set ::env(SBV_Z3) $savedZ3 }++    if {$rc && ![string match "*ENCODED*" $output] && ![string match "*DECODED*" $output]} {+        append output "\n\n** Call to crackNum failed! Make sure the value makes sense for the chosen format."+        append output "\n**"+        append output "\n**   Run: $cmd"+        append output "\n**"+        append output "\n**   Value : $val"+    }+    return $output+}++# ---------------------------------------------------------------------------+# Show output in the text widget+# ---------------------------------------------------------------------------+proc showOutput {text} {+    .output configure -state normal+    .output delete 1.0 end+    .output insert end $text+    .output configure -state disabled+}++proc crack {} {+    global state FORMAT_SECTIONS++    set sel $state(selection)+    if {$sel eq ""} return++    set out [runCrackNum]++    # Determine label for header+    set label ""+    foreach section $FORMAT_SECTIONS {+        foreach fmt [lindex $section 1] {+            if {[lindex $fmt 0] eq $sel} { set label [lindex $fmt 1] }+        }+    }++    if {[string match "*ENCODED*" $out]}      { set kind "Encoding in format" } \+    elseif {[string match "*DECODED*" $out]}  { set kind "Decoded using format" } \+    else                                      { set kind "Format" }++    showOutput "\[$kind: $label\]\n\n$out"+}++# ---------------------------------------------------------------------------+# Font size helpers+# ---------------------------------------------------------------------------+proc applyFontSize {} {+    global state+    .output configure -font [list Courier $state(fontSize)]+}++proc zoomIn  {} { incr ::state(fontSize); applyFontSize }+proc zoomOut {} {+    if {$::state(fontSize) > 6} { incr ::state(fontSize) -1; applyFontSize }+}++# ---------------------------------------------------------------------------+# Build UI+# ---------------------------------------------------------------------------++wm title . "CrackNum"+wm minsize . 1200 700+wm geometry . 1200x700++image create photo appIcon -data {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+}+wm iconphoto . appIcon++# ---- Menu bar ------------------------------------------------------------+menu .mb -tearoff 0+. configure -menu .mb++menu .mb.file -tearoff 0+.mb add cascade -label "File" -menu .mb.file -underline 0+.mb.file add command -label "Close" -accelerator "Ctrl+W" -command { destroy . }+bind . <Control-w> { destroy . }+bind . <Control-q> { destroy . }++menu .mb.edit -tearoff 0+.mb add cascade -label "Edit" -menu .mb.edit -underline 0+.mb.edit add command -label "Cut"        -accelerator "Ctrl+X" -command { event generate [focus] <<Cut>> }+.mb.edit add command -label "Copy"       -accelerator "Ctrl+C" -command { event generate [focus] <<Copy>> }+.mb.edit add command -label "Paste"      -accelerator "Ctrl+V" -command { event generate [focus] <<Paste>> }+.mb.edit add separator+.mb.edit add command -label "Select All" -accelerator "Ctrl+A" -command { event generate [focus] <<SelectAll>> }++# ---- Top bar: zoom, help, value entry ------------------------------------+frame .top+pack .top -fill x -padx 8 -pady 6++foreach {fr txt fnt cmd} {+    .top.zf1 "A" {TkDefaultFont 8}  zoomOut+    .top.zf2 "A" {TkDefaultFont 14} zoomIn+} {+    frame $fr -width 28 -height 28+    pack propagate $fr 0+    button $fr.b -text $txt -font $fnt -command $cmd+    pack $fr.b -fill both -expand yes+    pack $fr -side left -padx 2+}+button .top.help -text "?" -command { showOutput $::WELCOME }+pack .top.help -side left -padx 2++entry .top.val -textvariable state(value) -font {Courier 11} -width 28+pack .top.val -side right -padx {0 4}+bind .top.val <Return> crack++label .top.lbl -text "Value:"+pack .top.lbl -side right -padx {4 2}++# ---- Main pane: sidebar + output ----------------------------------------+frame .main+pack .main -fill both -expand yes -padx 8 -pady {0 8}++# Sidebar+frame .main.side -width 240+pack .main.side -side left -fill y -padx {0 6}+pack propagate .main.side 0++# Format list+ttk::style configure Treeview       -rowheight 22+ttk::style configure Treeview.Item  -padding {4 0}++ttk::treeview .main.side.lb -selectmode browse -show tree -height 18+pack .main.side.lb -fill both -expand yes++.main.side.lb tag configure hdr  -font {TkDefaultFont 9 bold}+.main.side.lb tag configure item -font {TkDefaultFont 9}++# Populate treeview; build item-id <-> format-id mappings+array set ITEM_FMT {}   ;# treeview item id -> format id+array set FMT_ITEM {}   ;# format id -> treeview item id++foreach section $FORMAT_SECTIONS {+    set title [lindex $section 0]+    set sid [.main.side.lb insert {} end -text $title -open yes -tags hdr]+    foreach fmt [lindex $section 1] {+        set fid  [lindex $fmt 0]+        set iid  [.main.side.lb insert $sid end -text [lindex $fmt 1] -tags item]+        set ITEM_FMT($iid) $fid+        set FMT_ITEM($fid) $iid+    }+}++bind .main.side.lb <<TreeviewSelect>> {+    set sel [.main.side.lb selection]+    if {$sel ne "" && [info exists ITEM_FMT($sel)]} {+        set state(selection) $ITEM_FMT($sel)+        crack+    } else {+        .main.side.lb selection remove $sel+    }+}++# Rounding+frame .main.side.rm+pack .main.side.rm -fill x -pady {6 0}+label .main.side.rm.lbl -text "Rounding mode:" -anchor w+pack .main.side.rm.lbl -fill x+ttk::combobox .main.side.rm.cb -state readonly -width 28+pack .main.side.rm.cb -fill x++foreach rm {RNE RNA RTP RTN RTZ} { lappend rm_labels $::ROUNDING_LABELS($rm) }+.main.side.rm.cb configure -values $rm_labels+# Show the full label in the combo but store the code in state(rounding)+proc rmLabel2Code {label} {+    foreach rm {RNE RNA RTP RTN RTZ} {+        if {$::ROUNDING_LABELS($rm) eq $label} { return $rm }+    }+    return RNE+}+proc rmCode2Label {code} { return $::ROUNDING_LABELS($code) }+.main.side.rm.cb set [rmCode2Label $state(rounding)]+bind .main.side.rm.cb <<ComboboxSelected>> {+    set state(rounding) [rmLabel2Code [.main.side.rm.cb get]]+    crack+}++# Lanes (decoding only; crackNum rejects -l when encoding)+frame .main.side.ln+pack .main.side.ln -fill x -pady {6 0}+label .main.side.ln.l -text "Lanes:"+pack  .main.side.ln.l -side left+entry .main.side.ln.e -textvariable state(lanes) -width 6 -justify right \+    -font {Courier 11}+pack  .main.side.ln.e -side right+bind  .main.side.ln.e <Return> crack++label .main.side.ln2 \+    -text "(lanes apply to decoding only)" \+    -font {TkDefaultFont 8} -foreground gray -wraplength 200 -justify left+pack .main.side.ln2 -fill x++# Custom parameters+labelframe .main.side.custom -text "Custom parameters" -padx 4 -pady 4+pack .main.side.custom -fill x -pady {8 0}++frame .main.side.custom.bw+pack .main.side.custom.bw -fill x -pady 2+label .main.side.custom.bw.l -text "Total width:"+pack  .main.side.custom.bw.l -side left+entry .main.side.custom.bw.e -textvariable state(bitWidth) -width 6 -justify right \+    -font {Courier 11}+pack  .main.side.custom.bw.e -side right+bind  .main.side.custom.bw.e <Return> crack++frame .main.side.custom.ew+pack .main.side.custom.ew -fill x -pady 2+label .main.side.custom.ew.l -text "Exponent width:"+pack  .main.side.custom.ew.l -side left+entry .main.side.custom.ew.e -textvariable state(expWidth) -width 6 -justify right \+    -font {Courier 11}+pack  .main.side.custom.ew.e -side right+bind  .main.side.custom.ew.e <Return> crack++label .main.side.custom.note \+    -text "(exponent width applies to custom floats)" \+    -font {TkDefaultFont 8} -foreground gray -wraplength 200 -justify left+pack .main.side.custom.note -fill x -pady {4 0}++# Output pane+frame .main.out+pack .main.out -side left -fill both -expand yes++text .output -state disabled -wrap none \+    -font [list Courier $state(fontSize)] \+    -padx 8 -pady 8 \+    -xscrollcommand {.main.out.sx set} \+    -yscrollcommand {.main.out.sy set}+scrollbar .main.out.sy -orient vertical   -command {.output yview}+scrollbar .main.out.sx -orient horizontal -command {.output xview}++grid .output       .main.out.sy -in .main.out -sticky nsew+grid .main.out.sx               -in .main.out -sticky ew+grid columnconfigure .main.out 0 -weight 1+grid rowconfigure    .main.out 0 -weight 1++# ---------------------------------------------------------------------------+# Parse crackNum-style command-line args (forwarded by `crackNum --gui ...`)+# ---------------------------------------------------------------------------+proc parseArgs {argv} {+    global state FORMAT_SECTIONS FMT_ITEM++    set values {}+    set i 0+    while {$i < [llength $argv]} {+        set a [lindex $argv $i]+        if {$a eq "--"} {+            lappend values {*}[lrange $argv [expr {$i+1}] end]+            break+        }+        if {[string match "-f*" $a]} {+            set v [string tolower [string range $a 2 end]]+            switch $v {+                sp   { set state(selection) fsp }+                dp   { set state(selection) fdp }+                hp   { set state(selection) fhp }+                bp   { set state(selection) fbp }+                e4m3 { set state(selection) fe4m3 }+                e5m2 { set state(selection) fe5m2 }+                fp4  { set state(selection) ffp4 }+                default {+                    if {[regexp {^(\d+)\+(\d+)$} $v _ e s]} {+                        set state(selection) fcs+                        set state(expWidth)  $e+                        set state(bitWidth)  [expr {$e + $s}]+                    }+                }+            }+        } elseif {[string match "-w*" $a]} {+            set v [string range $a 2 end]+            if {$v in {8 16 32 64}} { set state(selection) w$v } \+            elseif {[string is integer -strict $v] && $v > 0} {+                set state(selection) wcs+                set state(bitWidth) $v+            }+        } elseif {[string match "-i*" $a]} {+            set v [string range $a 2 end]+            if {$v in {8 16 32 64}} { set state(selection) i$v } \+            elseif {[string is integer -strict $v] && $v > 0} {+                set state(selection) ics+                set state(bitWidth) $v+            }+        } elseif {[string match "-r*" $a]} {+            set rm [string toupper [string range $a 2 end]]+            if {$rm in {RNE RNA RTP RTN RTZ}} { set state(rounding) $rm }+        } elseif {[string match "-l*" $a]} {+            set v [string range $a 2 end]+            if {[string is integer -strict $v] && $v > 0} { set state(lanes) $v }+        } elseif {![string match "-*" $a]} {+            lappend values $a+        }+        incr i+    }+    if {[llength $values]} { set state(value) [join $values " "] }++    # Sync treeview selection highlight+    if {$state(selection) ne "" && [info exists FMT_ITEM($state(selection))]} {+        set iid $FMT_ITEM($state(selection))+        .main.side.lb selection set $iid+        .main.side.lb see $iid+    }++    # Sync rounding combo label. Must happen even when no format was given:+    # `crackNum --gui -rRTZ` sets state(rounding) but leaves no selection.+    .main.side.rm.cb set [rmCode2Label $state(rounding)]+}++# ---------------------------------------------------------------------------+# Start+# ---------------------------------------------------------------------------+showOutput $WELCOME+parseArgs $argv+if {$state(selection) ne ""} { crack }+focus .top.val+.top.val icursor end
README.md view
@@ -115,35 +115,48 @@              Hex: 0x1.f18p-8 ``` -### Graphical interface (macOS, optional)+### Graphical interface (optional) -Optionally, crackNum comes with a native macOS GUI: pick a format on the left,-type a value, and see the encoding/decoding in detail. It is entirely optional —-crackNum is fully functional as a command-line tool without it. The GUI is just-a thin front-end that calls the `crackNum` binary underneath, so it supports-exactly the same formats.+Optionally, crackNum comes with a GUI: pick a format on the left, type a value,+and see the encoding/decoding in detail. It is entirely optional — crackNum is+fully functional as a command-line tool without it. The GUI is just a thin+front-end that calls the `crackNum` binary underneath, so it supports exactly+the same formats. -![crackNum macOS GUI](https://raw.githubusercontent.com/LeventErkok/crackNum/master/crackNumGUI.png)+![crackNum GUI](https://raw.githubusercontent.com/LeventErkok/crackNum/master/crackNumGUI.png) -Building it requires the Swift compiler that comes with the Xcode Command Line-Tools (`xcode-select --install`). From a checkout of this repository:+**macOS** — a native Swift/AppKit app (`GUI/swiftGUI/`). Building requires the+Swift compiler that comes with the Xcode Command Line Tools+(`xcode-select --install`):  ```-$ cd gui+$ cd GUI/swiftGUI $ make install      # builds CrackNum.app and copies it into /Applications ``` -Once installed, launch it from Spotlight/Launchpad, or straight from the-command line via the `--gui` option, which forwards any format/rounding flags-and value to the app:+**Linux** — a Tcl/Tk script (`GUI/tclGUI/crackNum.tcl`). The script ships with the+package and is installed alongside the binary, so there is nothing to build; you+only need `wish` (Tk 8.6+):  ```+$ nix profile install nixpkgs#tk   # or: sudo apt install tk / sudo dnf install tk+```++Then `crackNum --gui` just works. If you want to run a modified copy of the+script, either put it on your PATH as `crackNum.tcl`, or point at it directly+with `CRACKNUM_TCL=/path/to/crackNum.tcl`. See+[`GUI/tclGUI/README.md`](GUI/tclGUI/README.md) for details.++On both platforms, launch the GUI from the command line via the `--gui` option,+which forwards any format/rounding flags and value to the app:++``` $ crackNum --gui                 -- open the graphical interface $ crackNum --gui -fsp 2.5        -- open it with single-precision selected, and 2.5 cracked $ crackNum --gui 0xdeadbeef      -- open it pre-filled with a value to decode ``` -See [`gui/README.md`](gui/README.md) for more details and other build targets.+See [`GUI/swiftGUI/README.md`](GUI/swiftGUI/README.md) for macOS build details and other build targets.  ### Usage info ```@@ -156,7 +169,7 @@   -h, -?    --help     print help, with examples   -v        --version  print version info   -d        --debug    debug mode, developers only-            --gui      launch the graphical interface (macOS)+            --gui      launch the graphical interface  Examples:  Encoding:@@ -181,7 +194,7 @@    crackNum -ffp4    0b0111                -- decode as an FP4 (E2M1) float    crackNum -l4 -fhp 64\'hbdffaaffdc71fc60 -- decode as half-precision float over 4 lanes using verilog notation - GUI (macOS):+ GUI:    crackNum --gui                     -- launch the graphical interface    crackNum --gui 0xdeadbeef          -- launch the GUI, pre-filled with the given value 
crackNum.cabal view
@@ -1,6 +1,6 @@ Cabal-version      : 2.2 Name               : crackNum-Version            : 3.17+Version            : 3.18 Synopsis           : Crack various integer and floating-point data formats Description        : Crack IEEE-754 float formats and arbitrary sized words and integers, showing the layout.                      .@@ -13,7 +13,12 @@ Copyright          : Levent Erkok Category           : Tools Build-type         : Simple-Extra-Source-Files : README.md, COPYRIGHT, CHANGES.md+Extra-Source-Files : README.md, COPYRIGHT, CHANGES.md, GUI/tclGUI/README.md++-- The Tcl/Tk GUI is a data-file (not merely an extra-source-file) so that it is+-- actually installed alongside the binary, and can be found at run time via+-- Paths_crackNum.getDataFileName. See 'locateTcl' in Main.hs.+Data-files         : GUI/tclGUI/crackNum.tcl  source-repository head     type:       git
src/CrackNum/Main.hs view
@@ -34,6 +34,7 @@ import System.Console.GetOpt (ArgOrder(Permute), getOpt, ArgDescr(..), OptDescr(..), usageInfo) import System.Exit           (exitFailure, ExitCode(..)) import System.IO             (hPutStr, stderr)+import System.Directory      (findExecutable, doesFileExist) import System.Process        (rawSystem) import qualified System.Info as Info @@ -48,7 +49,7 @@ import qualified Data.SBV as SBV  import Data.Version    (showVersion)-import Paths_crackNum  (version)+import Paths_crackNum  (version, getDataFileName)  import CrackNum.TestSuite @@ -228,7 +229,7 @@     , Option "h?" ["help"]    (NoArg Help)                             "print help, with examples"     , Option "v"  ["version"] (NoArg Version)                          "print version info"     , Option "d"  ["debug"]   (NoArg Debug)                            "debug mode, developers only"-    , Option ""   ["gui"]     (NoArg GUI)                              "launch the graphical interface (macOS)"+    , Option ""   ["gui"]     (NoArg GUI)                              "launch the graphical interface"     ]  -- | Help info@@ -261,7 +262,7 @@                             , "   " ++ pn ++ " -ffp4    0b0111                -- decode as an FP4 (E2M1) float"                             , "   " ++ pn ++ " -l4 -fhp 64\\'hbdffaaffdc71fc60 -- decode as half-precision float over 4 lanes using verilog notation"                             , ""-                            , " GUI (macOS):"+                            , " GUI:"                             , "   " ++ pn ++ " --gui                     -- launch the graphical interface"                             , "   " ++ pn ++ " --gui 0xdeadbeef          -- launch the GUI, pre-filled with the given value"                             , ""@@ -285,18 +286,66 @@ die xs = do hPutStr stderr $ unlines $ "ERROR:" : map ("  " ++) xs             exitFailure --- | Launch the graphical interface (macOS only), forwarding all remaining arguments+-- | Where the Tcl/Tk GUI script lives relative to the package root; also its+-- location within the installed data-directory. (See Data-files in the cabal file.)+tclRelPath :: FilePath+tclRelPath = "GUI/tclGUI/crackNum.tcl"++-- | Locate the Tcl/Tk GUI script. Normally it is installed together with the+-- binary, so this just works; we look in three places, in order:+--+--   1. $CRACKNUM_TCL, if set: an explicit override, mirroring $CRACKNUM_GUI on macOS.+--   2. The PATH, so a source checkout can shadow the installed copy while hacking.+--   3. The copy cabal installed in our data-directory.+locateTcl :: IO FilePath+locateTcl = do mbEnv <- lookupEnv "CRACKNUM_TCL"+               case mbEnv of+                 Just p  -> do ok <- doesFileExist p+                               if ok+                                  then pure p+                                  else die [ "The CRACKNUM_TCL environment variable is set, but does not name a file:"+                                           , ""+                                           , "    " ++ p+                                           ]+                 Nothing -> do mbPath <- findExecutable "crackNum.tcl"+                               case mbPath of+                                 Just p  -> pure p+                                 Nothing -> do installed <- getDataFileName tclRelPath+                                               ok        <- doesFileExist installed+                                               if ok+                                                  then pure installed+                                                  else die (noTcl installed)+  where noTcl installed =+             [ "Cannot find the CrackNum GUI script (crackNum.tcl)."+             , ""+             , "Looked in:"+             , "  $CRACKNUM_TCL                 (not set)"+             , "  crackNum.tcl on your PATH     (not found)"+             , "  " ++ installed+             , ""+             , "This script is normally installed along with crackNum, so seeing this"+             , "means the installed copy is missing or the binary has been moved."+             , ""+             , "If you have a source checkout, point at it directly:"+             , ""+             , "    export CRACKNUM_TCL=/path/to/crackNum/" ++ tclRelPath+             , ""+             , "Otherwise, get a copy of the sources with either of:"+             , ""+             , "    cabal get crackNum"+             , "    git clone http://github.com/LeventErkok/crackNum.git"+             ]++-- | Launch the graphical interface, forwarding all remaining arguments -- (format flags, rounding mode, and/or the value to crack) so the GUI can preselect--- them. The GUI itself calls back into this executable to do the actual cracking. The--- location of the app can be overridden with the CRACKNUM_GUI environment variable--- (pointing at the .app bundle); otherwise we ask LaunchServices to find it by name.+-- them. The GUI itself calls back into this executable to do the actual cracking.+--+-- On macOS the GUI is a Swift/AppKit app; CRACKNUM_GUI can override the .app bundle+-- location. On Linux the GUI is a Tcl/Tk script; both 'wish' and 'crackNum.tcl' are+-- located via PATH. launchGUI :: [String] -> IO () launchGUI vals-  | Info.os /= "darwin"-  = die [ "The --gui option is only available on macOS."-        , "On other platforms, use crackNum directly from the command line."-        ]-  | True+  | Info.os == "darwin"   = do mbApp <- lookupEnv "CRACKNUM_GUI"        let args = case mbApp of                     Just p  -> ["-n", p,                "--args"] ++ vals@@ -310,11 +359,34 @@                               , "get the crackNum sources and build the GUI (macOS 13+, Swift toolchain):"                               , ""                               , "    git clone http://github.com/LeventErkok/crackNum.git"-                              , "    cd crackNum/gui"+                              , "    cd crackNum/GUI/swiftGUI"                               , "    make install       # builds and copies CrackNum.app into /Applications"                               , ""                               , "Then re-run: crackNum --gui" ++ (if null vals then "" else ' ' : unwords vals)                               ]+  | Info.os == "linux"+  = do mbWish <- findExecutable "wish"+       wish   <- case mbWish of+                   Just w  -> pure w+                   Nothing -> die [ "Cannot find 'wish' on your PATH."+                                  , "Install Tcl/Tk to get wish, e.g.:"+                                  , ""+                                  , "    nix profile install nixpkgs#tk"+                                  , "    sudo apt install tk       # Debian/Ubuntu"+                                  , "    sudo dnf install tk       # RHEL/Fedora"+                                  ]+       tcl    <- locateTcl+       ec     <- rawSystem wish (tcl : vals)+       case ec of+         ExitSuccess   -> pure ()+         ExitFailure _ -> die [ "Unable to launch the CrackNum GUI."+                              , ""+                              , "Tried: " ++ wish ++ " " ++ tcl+                              ]+  | True+  = die [ "The --gui option is not supported on this platform (" ++ Info.os ++ ")."+        , "Use crackNum directly from the command line."+        ]   -- | main entry point to crackNum@@ -369,10 +441,26 @@                                                     | tryInfer = fromMaybe lanesGiven lanesInferred                                                     | True     = lanesGiven -                                              if decode-                                                 then decodeAllLanes isVerilog debug lanes kind    arg-                                                 else encodeLane               debug lanes kind rm arg+                                              let act | decode = decodeAllLanes isVerilog debug lanes kind    arg+                                                      | True   = encodeLane               debug lanes kind rm arg +                                              act `C.catch` solverLimitation kind++-- | We accept exponent/significand sizes down to 1 bit, but SMTLib's FloatingPoint+-- sort (and hence z3) requires at least 2 of each. Rather than letting such a format+-- surface as a raw solver exception with a backtrace, report it as a plain error.+-- Anything else is re-thrown untouched.+solverLimitation :: NKind -> SBVException -> IO a+solverLimitation kind e = case kind of+                            SFloat (FP eb sb) | eb < 2 || sb < 2 -> die [ "The solver does not support this format:"+                                                                        , "  " ++ plural eb "exponent bit" ++ ", " ++ plural sb "significand bit"+                                                                        , "z3 requires at least 2 of each."+                                                                        ]+                            _                                    -> C.throwIO e+  where plural :: Int -> String -> String+        plural 1 what = "1 " ++ what+        plural n what = show n ++ " " ++ what ++ "s"+ decodeAllLanes :: Bool -> Bool -> Int -> NKind -> String -> IO () decodeAllLanes isVerilog debug lanes kind arg = do    when (lanes < 0) $ die@@ -614,6 +702,12 @@         mapM_ (putStrLn . fixVal) $ takeWhile (not . isClassification) (lines (show ieeeResult))         mapM_ putStrLn            $ dropWhile (not . isClassification) (lines modifiedResult) +-- | The canonical quiet-NaN pattern for a float with @eb@ exponent bits and @sb@+-- significand bits (including the implicit one): sign 0, all-ones exponent, and only+-- the leading stored significand bit set. For single-precision this is 0x7FC00000.+canonicalNaN :: Int -> Int -> Integer+canonicalNaN eb sb = (2 ^ eb - 1) * 2 ^ (sb - 1) + 2 ^ (sb - 2)+ -- | Encoding encodeLane :: Bool -> Int -> NKind -> RM -> String -> IO () encodeLane debug lanes num rm inp@@ -626,8 +720,17 @@       SInt   n -> print =<< ei True  n       SWord  n -> print =<< ei False n       SFloat s -> ef s (s == E5M2)-  where satCmd = satWith z3{crackNum=True, verbose=debug, isNonModelVar = (/= "ENCODED")}+  where cfg    = z3{crackNum=True, verbose=debug, isNonModelVar = (/= "ENCODED")}+        satCmd = satWith cfg +        -- SMTLib's FloatingPoint sort has exactly one NaN value: the solver answers+        -- with the abstract (_ NaN eb sb), so the concrete bit-pattern we display is+        -- picked when that abstract value is materialized, and is not stable across+        -- solver/library upgrades. Pin it to the canonical quiet NaN, the same way+        -- the E4M3 path does. (We still note that the representation isn't unique.)+        satCmdNaN :: Int -> Int -> Predicate -> IO SatResult+        satCmdNaN eb sb = satWith cfg{crackNumSurfaceVals = [("ENCODED", canonicalNaN eb sb)]}+         ei :: Bool -> Int -> IO SatResult         ei sgn n = case reads inp of                      [(v :: Integer, "")] -> satCmd $ p v@@ -655,21 +758,27 @@          ef :: FP -> Bool -> IO ()         ef SP _ = case reads (fixup True inp) of-                    [(v :: Float, "")] -> do print =<< satCmd (p v)+                    [(v :: Float, "")] -> do print =<< run v (p v)                                              note $ snd $ convert 8 24                     _                  -> ef (FP 8 24) False          where p :: Float -> Predicate                p f = do x <- sFloat "ENCODED"                         pure $ x .=== literal f +               run f | isNaN f = satCmdNaN 8 24+                     | True    = satCmd+         ef DP _ = case reads (fixup True inp) of-                    [(v :: Double, "")] -> do print =<< satCmd (p v)+                    [(v :: Double, "")] -> do print =<< run v (p v)                                               note $ snd $ convert 11 53                     _                   -> ef (FP 11 53) False          where p :: Double -> Predicate                p d = do x <- sDouble "ENCODED"                         pure $ x .=== literal d +               run d | isNaN d = satCmdNaN 11 53+                     | True    = satCmd+         ef (FP i j) wasE5M2 = do let (v, mbS) = convert i j                                  if bfIsNaN v && fixup False inp /= "NaN"                                     then -- maybe it's a hexfloat?@@ -678,7 +787,9 @@                                             res <- satCmd (pRat hr)                                             if wasE5M2 then fixE5M2Type res                                                        else print res-                                    else do res <- satCmd (p v)+                                    else do let run | bfIsNaN v = satCmdNaN i j+                                                    | True      = satCmd+                                            res <- run (p v)                                             if wasE5M2 then fixE5M2Type res                                                        else print res                                             note mbS
src/CrackNum/TestSuite.hs view
@@ -157,5 +157,9 @@           , testGroup "Bad" [                gold "badInvocation0" "-f3+4 0b01"             ,  gold "badInvocation1" "-f3+4 0xFFFF"+            -- We accept 1-bit exponents/significands, but the solver needs 2 of+            -- each; make sure that surfaces as an error, not a raw exception.+            ,  gold "badInvocation2" "-f3+1 0.5"+            ,  gold "badInvocation3" "-f1+3 0b0000"             ]         ]