Hi,
I have a code (It solves Advent of Code 2015 day04 part 2, spoiler warning!) and it behaves weirdly in ReleaseFast mode. Debug mode is fine.
build.zig
const std = @import("std");
// Although this function looks imperative, note that its job is to
// declaratively construct a build graph that will be executed by an external
// runner.
pub fn build(b: *std.Build) void {
// Standard target options allows the person running `zig build` to choose
// what target to build for. Here we do not override the defaults, which
// means any target is allowed, and the default is native. Other options
// for restricting supported target set are available.
const target = b.standardTargetOptions(.{});
// Standard optimization options allow the person running `zig build` to select
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not
// set a preferred release mode, allowing the user to decide how to optimize.
const optimize = b.standardOptimizeOption(.{});
const exe = b.addExecutable(.{
.name = "prog",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
// This declares intent for the executable to be installed into the
// standard location when the user invokes the "install" step (the default
// step when running `zig build`).
b.installArtifact(exe);
// This *creates* a Run step in the build graph, to be executed when another
// step is evaluated that depends on it. The next line below will establish
// such a dependency.
const run_cmd = b.addRunArtifact(exe);
// By making the run step depend on the install step, it will be run from the
// installation directory rather than directly from within the cache directory.
// This is not necessary, however, if the application depends on other installed
// files, this ensures they will be present and in the expected location.
run_cmd.step.dependOn(b.getInstallStep());
// This allows the user to pass arguments to the application in the build
// command itself, like this: `zig build run -- arg1 arg2 etc`
if (b.args) |args| {
run_cmd.addArgs(args);
}
// This creates a build step. It will be visible in the `zig build --help` menu,
// and can be selected like this: `zig build run`
// This will evaluate the `run` step rather than the default, which is "install".
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
const exe_unit_tests = b.addTest(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
// Similar to creating the run step earlier, this exposes a `test` step to
// the `zig build --help` menu, providing a way for the user to request
// running the unit tests.
const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&run_exe_unit_tests.step);
}
main.zig
const std = @import("std");
const stdout = std.io.getStdOut().writer();
const stderr = std.io.getStdErr().writer();
pub fn startsNZerosHex(inp: []const u8, n: usize) bool {
if (inp.len < @divFloor(n + 1, 2)) {
return false;
}
const n_is_even = @mod(n, 2) == 0;
const nev = if (n_is_even) n else n - 1;
for (inp[0..@divExact(nev, 2)]) |i| {
if (i != 0) {
return false;
}
}
if (!n_is_even) {
return inp[@divExact(nev, 2)] < 16;
}
return true;
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const args = try std.process.argsAlloc(allocator);
defer std.process.argsFree(allocator, args);
if (args.len < 2) {
try stderr.print("Usage: {s} <filename>\n", .{args[0]});
return error.InvalidArguments;
}
const filename = args[1];
const file = try std.fs.cwd().openFile(filename, .{});
defer file.close();
const file_size = try file.getEndPos();
const input = try allocator.alloc(u8, file_size);
defer allocator.free(input);
const bytes_read = try file.readAll(input);
if (bytes_read != file_size) {
try stderr.print("Warning: Read {d} bytes but expected {d}\n", .{ bytes_read, file_size });
}
// try stdout.print("File input is {s}\n", .{input}); // including this line ommits the error message.
var md5input = std.ArrayList(u8).init(allocator);
defer md5input.deinit();
const Md5 = std.crypto.hash.Md5;
var output: [Md5.digest_length]u8 = undefined;
var num: usize = 0;
while (!startsNZerosHex(&output, 6)) : (num += 1) {
md5input.clearRetainingCapacity();
_ = try md5input.writer().print("{s}{d}", .{ input[0 .. file_size - 1], num });
Md5.hash(md5input.items, &output, .{});
}
num -= 1;
try stdout.print("md5(\"{s}\") = ", .{md5input.items});
for (output) |i| {
try stdout.print("{x:0>2}", .{i});
}
try stdout.print(" -> {d}\n", .{num});
}
test.inp
bgvyzdsv
When I run it regularly it results in:
$ zig build run -- ./src/test.inp
md5("bgvyzdsv1038736") = 000000b1b64bf5eb55aad89986126953 -> 1038736
When I run it with ReleaseFast:
$ zig build -Doptimize=ReleaseFast run -- ./src/test.inp
Warning: Read 9 bytes but expected 9
and then hangs in an infinite loop.
In line 44 I test if the read bytes and the file size match. If they do not I will print out the values. However, as you can see in the above output, the read bytes and file size match, but the output is written anyway.
Interestingly if I remove everything after line 48 the warning is not printed anymore. Also if I uncomment line 48 where I print the file contents.
Sorry, but I could not find a way to reduce the code without this behavior disappearing.
What did I do wrong? And If I did something wrong, why does it work in debug mode but not in ReleaseFast mode?
Thanks for your help.