about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--Makefile5
-rw-r--r--Variables.zig109
-rw-r--r--synth.zig172
3 files changed, 244 insertions, 42 deletions
diff --git a/Makefile b/Makefile
index d707904..85f634e 100644
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,5 @@
 .POSIX:
-.PHONY: all clean install uninstall
+.PHONY: all clean check install uninstall
 
 CXXFLAGS += -g -std=c++23 -Wextra -Werror
 LDLIBS += -lcommon -ldyninstAPI -linstructionAPI -lparseAPI # dyninst
@@ -34,6 +34,9 @@ trace-call: trace-call.o helpers.o
 %: %.c
 	e9compile $<
 
+check: synth.zig
+	zig test $<
+
 install: $(BIN:%=$(BIN_PREFIX)%) $(DATA:%=$(DATA_DIR)/%)
 
 $(BIN_PREFIX)%: %
diff --git a/Variables.zig b/Variables.zig
index 99f7061..9732afa 100644
--- a/Variables.zig
+++ b/Variables.zig
@@ -20,27 +20,29 @@ const Allocator = std.mem.Allocator;
 const Writer = std.io.Writer;
 const assert = std.debug.assert;
 const bytesAsSlice = std.mem.bytesAsSlice;
+const comptimePrint = std.fmt.comptimePrint;
 const cwd = std.fs.cwd;
 const divCeil = std.math.divCeil;
-const fields = std.meta.fields;
+const eql = std.meta.eql;
+const expect = std.testing.expect;
+const maxInt = std.math.maxInt;
+const minInt = std.math.minInt;
 const std = @import("std");
+const tags = std.meta.tags;
 
 pub const RegisterEnum = enum(u4) {
     rax, rbx, rcx, rdx, rsi, rdi, rsp, rbp,
     r8, r9, r10, r11, r12, r13, r14, r15,
-
-    pub fn format(tag: RegisterEnum, writer: *Writer) Writer.Error!void {
-        try writer.print("{s}", .{ @tagName(tag) });
-    }
 };
-const Register = i64;
-const Registers = [fields(RegisterEnum).len]Register;
+pub const Register = i64;
+const Registers = [tags(RegisterEnum).len]Register;
 
 pub const signed_integers = .{ i64, i32, i16, i8 };
 const alignment = @alignOf(Register);
-comptime {
-    for (signed_integers) |Int|
-        assert(alignment >= @alignOf(Int));
+
+test alignment {
+    inline for (signed_integers) |Int|
+        try expect(alignment >= @alignOf(Int));
 }
 
 fn alignedSize(T: type, count: usize) !usize {
@@ -85,9 +87,9 @@ pub fn init(allocator: Allocator, stack_size: usize,
         assert(try file.getEndPos() == file_size);
         assert(try file.read(buffer) == file_size);
         var offset: usize = 0;
-        inline for (registers) |reg| {
+        inline for (registers) |register| {
             const slice = bytesAsSlice(Register, bytes[offset..]);
-            slice[index] = reg;
+            slice[index] = register;
             offset += try alignedSize(Register, count);
         }
         inline for (signed_integers) |Int| {
@@ -106,9 +108,82 @@ pub fn deinit(variables: Variables, allocator: Allocator) void {
     allocator.free(variables.bytes);
 }
 
-pub fn register(variables: Variables, name: RegisterEnum) ![]const Register {
-    const aligned_size = try alignedSize(Register, variables.samples);
-    const offset = aligned_size * @intFromEnum(name);
-    const slice = bytesAsSlice(Register, variables.bytes[offset..]);
-    return @alignCast(slice[0..variables.samples]);
+const ConstantEnum = enum(i64) {
+    max1 = maxInt(i1),
+    min2 = minInt(i2),
+    max2 = maxInt(i2),
+    min3 = minInt(i3),
+    max3 = maxInt(i3),
+    min4 = minInt(i4),
+    max4 = maxInt(i4),
+    min5 = minInt(i5),
+    max5 = maxInt(i5),
+    min6 = minInt(i6),
+    max6 = maxInt(i6),
+    min7 = minInt(i7),
+    max7 = maxInt(i7),
+    min8 = minInt(i8),
+    max8 = maxInt(i8),
+    min9 = minInt(i9),
+    min16 = minInt(i16),
+    max16 = maxInt(i16),
+    min17 = minInt(i17),
+    min32 = minInt(i32),
+    max32 = maxInt(i32),
+    min33 = minInt(i33),
+    min64 = minInt(i64),
+    max64 = maxInt(i64),
+};
+
+pub const Query = union(enum) {
+    constant: ConstantEnum,
+    register: RegisterEnum,
+    stack: usize,
+
+    pub fn all(allocator: Allocator) Allocator.Error![]Query {
+        const constants = tags(ConstantEnum);
+        const registers = tags(RegisterEnum);
+        const n = constants.len + registers.len;
+        const queries = try allocator.alloc(Query, n);
+        for (queries[0..constants.len], constants) |*dest, src|
+            dest.* = .{ .constant = src };
+        for (queries[constants.len..][0..registers.len],
+             registers) |*dest, src|
+            dest.* = .{ .register = src };
+        return queries;
+    }
+
+    pub fn skip(q: Query, r: Query, s: Query) bool {
+        return eql(q, r) or eql(r, s) or eql(s, q)
+            or q == .constant and r == .constant and s == .constant
+            or q != .constant and r != .constant and s != .constant;
+    }
+
+    pub fn format(query: Query, writer: *Writer) Writer.Error!void {
+        switch (query) {
+            inline .constant, .register => |tag|
+                try writer.print("{s}", .{ @tagName(tag) }),
+            .stack => unreachable,
+        }
+    }
+};
+
+pub fn get(variables: Variables, query: Query,
+           tmp: []Register) ![]const Register {
+    switch (query) {
+        .constant => |constant| switch (constant) {
+            inline else => |tag| {
+                for (tmp) |*register|
+                    register.* = @intFromEnum(tag);
+                return tmp;
+            },
+        },
+        .register => |tag| {
+            const aligned_size = try alignedSize(Register, variables.samples);
+            const offset = aligned_size * @intFromEnum(tag);
+            const slice = bytesAsSlice(Register, variables.bytes[offset..]);
+            return @alignCast(slice[0..variables.samples]);
+        },
+        .stack => unreachable,
+    }
 }
diff --git a/synth.zig b/synth.zig
index 3e04407..e82f85b 100644
--- a/synth.zig
+++ b/synth.zig
@@ -16,6 +16,7 @@
 // You should have received a copy of the GNU Affero General Public License
 // along with taosc.  If not, see <https://www.gnu.org/licenses/>.
 
+const Allocator = std.mem.Allocator;
 const ArenaAllocator = std.heap.ArenaAllocator;
 const CompareOperator = std.math.CompareOperator;
 const Writer = std.io.Writer;
@@ -23,37 +24,107 @@ const argsAlloc = std.process.argsAlloc;
 const argsFree = std.process.argsFree;
 const assert = std.debug.assert;
 const compare = std.math.compare;
-const enums = std.enums;
+const divTrunc = std.math.divTrunc;
 const exit = std.process.exit;
 const page_allocator = std.heap.page_allocator;
 const parseUnsigned = std.fmt.parseUnsigned;
 const print = std.debug.print;
+const rem = std.math.rem;
+const shl = std.math.shl;
+const shr = std.math.shr;
 const std = @import("std");
 const stdout = std.fs.File.stdout;
+const tags = std.meta.tags;
 
+const Register = Variables.Register;
 const RegisterEnum = Variables.RegisterEnum;
 const Variables = @import("Variables.zig");
 const signed_integers = Variables.signed_integers;
 
-const Variable = RegisterEnum;
-const Expression = Variable;
+comptime {
+    _ = Variables; // for test discovery
+}
+
+const UnaryOperator = enum {
+    @"+",
+    @"-",
+    @"~",
+
+    fn apply(op: UnaryOperator, val: Register) Register {
+        return switch (op) {
+            .@"+" => val,
+            .@"-" => -%val,
+            .@"~" => ~val,
+        };
+    }
+};
+
+const Unary = struct {
+    op: UnaryOperator,
+    val: Variables.Query,
+
+    pub fn format(un: Unary, writer: *Writer) Writer.Error!void {
+        try writer.print("{s}{f}", .{ @tagName(un.op), un.val });
+    }
+};
+
+const ArithmeticOperator = enum {
+    @"+", @"-", @"*", @"/", @"%",
+    @"&", @"|", @"^", @"<<", @">>",
+
+    fn apply(op: ArithmeticOperator, lhs: Register, rhs: Register) !Register {
+        return switch (op) {
+            .@"+" => lhs +% rhs,
+            .@"-" => lhs -% rhs,
+            .@"*" => lhs *% rhs,
+            .@"/" => try divTrunc(Register, lhs, rhs),
+            .@"%" => try rem(Register, lhs, rhs),
+            .@"&" => lhs & rhs,
+            .@"|" => lhs | rhs,
+            .@"^" => lhs ^ rhs,
+            .@"<<" => shl(Register, lhs, rhs),
+            .@">>" => shr(Register, lhs, rhs),
+        };
+    }
+};
+
+const Arithmetic = struct {
+    lhs: Unary,
+    op: ArithmeticOperator,
+    rhs: Unary,
+
+    pub fn format(arith: Arithmetic, writer: *Writer) Writer.Error!void {
+        try writer.print("{f} {s} {f}", .{
+            arith.lhs,
+            @tagName(arith.op),
+            arith.rhs,
+        });
+    }
+};
+
 const Comparison = struct {
-    lhs: Expression,
+    lhs: Unary,
     op: CompareOperator,
-    rhs: Expression,
+    rhs: Arithmetic,
 
-    fn check(cmp: Comparison, bot: Variables, top: Variables) !bool {
-        for (try bot.register(cmp.lhs), try bot.register(cmp.rhs)) |lhs, rhs|
-            if (compare(lhs, cmp.op, rhs))
+    fn check(cmp: Comparison, vars: Variables, expected: bool,
+             tmp: Temporaries) !bool {
+        for (try vars.get(cmp.lhs.val, tmp[0]),
+             try vars.get(cmp.rhs.lhs.val, tmp[1]),
+             try vars.get(cmp.rhs.rhs.val, tmp[2])) |v0, v1, v2| {
+            const arith = cmp.rhs.op.apply(cmp.rhs.lhs.op.apply(v1),
+                                           cmp.rhs.lhs.op.apply(v2)) catch
                 return false;
-        for (try top.register(cmp.lhs), try top.register(cmp.rhs)) |lhs, rhs|
-            if (!compare(lhs, cmp.op, rhs))
+            const actual = compare(cmp.lhs.op.apply(v0), cmp.op, arith);
+            if (if (expected) !actual else actual)
                 return false;
+        }
         return true;
     }
 
     pub fn format(cmp: Comparison, writer: *Writer) Writer.Error!void {
-        try writer.print("{s} {f} {f}", .{
+        try writer.print("{f} {s} {f}", .{
+            cmp.lhs,
             switch (cmp.op) {
                 .lt => "<",
                 .lte => "<=",
@@ -62,12 +133,74 @@ const Comparison = struct {
                 .gt => ">",
                 .neq => "!=",
             },
-            cmp.lhs,
             cmp.rhs,
         });
     }
 };
 
+const Temporaries = [3][]Register;
+
+fn process(writer: *Writer, bot: Variables, top: Variables,
+           v0: Variables.Query, v1: Variables.Query, v2: Variables.Query,
+           o0: UnaryOperator, o1: UnaryOperator, o2: UnaryOperator,
+           arith: ArithmeticOperator, cmp: CompareOperator,
+           bot_tmp: Temporaries, top_tmp: Temporaries) !void {
+    const predicate = Comparison{
+        .lhs = .{ .op = o0, .val = v0 },
+        .op = cmp,
+        .rhs = .{
+            .lhs = .{ .op = o1, .val = v1 },
+            .op = arith,
+            .rhs = .{ .op = o2, .val = v2 },
+        },
+    };
+    if (try predicate.check(bot, false, bot_tmp))
+        return;
+    if (try predicate.check(top, true, top_tmp))
+        return;
+    try writer.print("{f}\n", .{ predicate });
+}
+
+fn mineOperator(writer: *Writer, bot: Variables, top: Variables,
+                v0: Variables.Query, v1: Variables.Query, v2: Variables.Query,
+                bot_tmp: Temporaries, top_tmp: Temporaries) !void {
+    for (tags(UnaryOperator)) |o0|
+        for (tags(UnaryOperator)) |o1|
+            for (tags(UnaryOperator)) |o2|
+                for (tags(ArithmeticOperator)) |arith|
+                    for (tags(CompareOperator)) |cmp|
+                        try process(writer, bot, top, v0, v1, v2, o0, o1, o2,
+                                    arith, cmp, bot_tmp, top_tmp);
+}
+
+fn alloc_tmp(allocator: Allocator, len: usize) Allocator.Error!Temporaries {
+    var tmp = [_][]Register{ &.{} } ** 3;
+    errdefer free_tmp(allocator, tmp);
+    for (&tmp) |*variable|
+        variable.* = try allocator.alloc(Register, len);
+    return tmp;
+}
+
+fn free_tmp(allocator: Allocator, tmp: Temporaries) void {
+    for (tmp) |variable|
+        allocator.free(variable);
+}
+
+fn mine(allocator: Allocator, writer: *Writer,
+        bot: Variables, top: Variables) !void {
+    const bot_tmp = try alloc_tmp(allocator, bot.samples);
+    defer free_tmp(allocator, bot_tmp);
+    const top_tmp = try alloc_tmp(allocator, top.samples);
+    defer free_tmp(allocator, top_tmp);
+
+    const variables = try Variables.Query.all(allocator);
+    defer allocator.free(variables);
+    for (variables) |v0| for (variables) |v1| for (variables) |v2|
+        if (!v0.skip(v1, v2))
+            try mineOperator(writer, bot, top, v0, v1, v2, bot_tmp, top_tmp);
+    try writer.flush();
+}
+
 pub fn main() !void {
     var buffer: [80]u8 = undefined;
     var writer = stdout().writer(&buffer);
@@ -84,20 +217,11 @@ pub fn main() !void {
         exit(1);
     }
     const stack_size = try parseUnsigned(usize, args[1], 0);
-    inline for (signed_integers) |Int|
-        assert(stack_size % @sizeOf(Int) == 0);
+    inline for (signed_integers) |SignedInt|
+        assert(stack_size % @sizeOf(SignedInt) == 0);
     const bot = try Variables.init(allocator, stack_size, args[2]);
     defer bot.deinit(allocator);
     const top = try Variables.init(allocator, stack_size, args[3]);
     defer top.deinit(allocator);
-
-    for (enums.values(RegisterEnum)) |lhs|
-        for (enums.values(RegisterEnum)) |rhs|
-            if (@intFromEnum(lhs) < @intFromEnum(rhs))
-                for (enums.values(CompareOperator)) |op| {
-                    const cmp = Comparison{ .lhs = lhs, .op = op, .rhs = rhs };
-                    if (try cmp.check(bot, top))
-                        try writer.interface.print("{f}\n", .{ cmp });
-                };
-    try writer.interface.flush();
+    try mine(allocator, &writer.interface, bot, top);
 }