#Determine if a function can be evaluated at comptime

1 messages · Page 1 of 1 (latest)

formal horizon
#

Is it possible to determine if the result of a function can be determined at comptime, here is some code where it would be useful

const builtin = @import("builtin");

extern fn systemGetHandle() u64;
extern fn useHandle(handle: u64) u64;

fn getHandle() u64 {
    return if (builtin.os.tag == .windows) systemGetHandle() else 1;
}

const handle_comptime_known = builtin.os.tag != .windows;

pub const Wrapper = struct {
    handle: if (handle_comptime_known) void else u64,

    const Self = @This();

    pub fn init() Self {
        if (!handle_comptime_known) {
            return .{.handle = getHandle()};
        } else {
            return .{.handle = {}};
        }
    }

    pub fn action(self: Self) u64 {
        if (!handle_comptime_known) {
            return useHandle(self.handle);
        } else {
            return useHandle(comptime getHandle());
        }
    }
};

I would like to make handle_comptime_known not be dependent on the internal logic of getHandle(), such as the case where it is provided by a library.

opal haven
#

it is not possible

formal horizon
opal haven
formal horizon
#

Though also note that if that was added it still would not solve this use case

opal haven
#

im not sure how introducing such a builtin would enable writing optimal code

formal horizon
opal haven
#

it certainly doesn't help writing code optimal to read

#

do you have an example in the wild where this case comes up that the optimizer also cannot optimize

formal horizon
#

also here is an example (based off the top code) it doesn't optimize

#
extern fn systemGetHandle() u64;
extern fn useHandle(handle: u64) u64;

fn getHandle() u64 {
    return if (builtin.os.tag == .windows) systemGetHandle() else 1;
}

pub const Wrapper = struct {
    handle: u64,

    const Self = @This();

    pub fn init() Self {
        return .{.handle = getHandle()};

    }

    pub fn action(self: Self) u64 {
        return useHandle(self.handle);

    }
};

export fn foo(w_opq: *anyopaque) u64 {
    const w: *Wrapper = @alignCast(@ptrCast(w_opq));
    return w.action();
}
foo:
        mov     rdi, qword ptr [rdi]
        jmp     useHandle@PLT
opal haven
#

that's till contrived, because if you're getting stdout you don't need an extern fn

#

of course an extern fn can't be optimized away

formal horizon
#

getting the handle in the example doesn't use an extern function (except for windows)

#

the outputed assembly above is the same for windows and linux also

#

Gives the same assembly

export fn foo(w: *Wrapper) u64 {
    return w.action();
}
left crest
#

why wouldnt it

#

you never call init

formal horizon
#

Ah I see. It is impossible for the second version to be optimized compared to the first with a comptime known handle

#

In larger code bases it is basically impossible for the optimizer to see the initialization

opal haven
#

and if the intent of getHandle is to get stdout, you can do that with 4 movs on windows, which is likely a lot more efficient than an extern fn call

opal haven
#

hiding the initialization complexity behind an extern fn on windows makes this example look a lot worse than it really should be

formal horizon
left crest
#

movs can be evaluated at comptime in the optimizer

formal horizon
#

not to runtime memory

left crest
#

i mean, yeah, they cant