aboutsummaryrefslogtreecommitdiff
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);
}