Skip to content

introduce @call and remove other builtin calls #3856

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Dec 6, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
179 changes: 93 additions & 86 deletions doc/langref.html.in
Original file line number Diff line number Diff line change
Expand Up @@ -6841,6 +6841,99 @@ async fn func(y: *i32) void {
</p>
{#header_close#}

{#header_open|@call#}
<pre>{#syntax#}@call(options: std.builtin.CallOptions, function: var, args: var) var{#endsyntax#}</pre>
<p>
Calls a function, in the same way that invoking an expression with parentheses does:
</p>
{#code_begin|test|call#}
const assert = @import("std").debug.assert;

test "noinline function call" {
assert(@call(.{}, add, .{3, 9}) == 12);
}

fn add(a: i32, b: i32) i32 {
return a + b;
}
{#code_end#}
<p>
{#syntax#}@call{#endsyntax#} allows more flexibility than normal function call syntax does. The
{#syntax#}CallOptions{#endsyntax#} struct is reproduced here:
</p>
{#code_begin|syntax#}
pub const CallOptions = struct {
modifier: Modifier = .auto,
stack: ?[]align(std.Target.stack_align) u8 = null,

pub const Modifier = enum {
/// Equivalent to function call syntax.
auto,

/// Prevents tail call optimization. This guarantees that the return
/// address will point to the callsite, as opposed to the callsite's
/// callsite. If the call is otherwise required to be tail-called
/// or inlined, a compile error is emitted instead.
never_tail,

/// Guarantees that the call will not be inlined. If the call is
/// otherwise required to be inlined, a compile error is emitted instead.
never_inline,

/// Asserts that the function call will not suspend. This allows a
/// non-async function to call an async function.
no_async,

/// Guarantees that the call will be generated with tail call optimization.
/// If this is not possible, a compile error is emitted instead.
always_tail,

/// Guarantees that the call will inlined at the callsite.
/// If this is not possible, a compile error is emitted instead.
always_inline,

/// Evaluates the call at compile-time. If the call cannot be completed at
/// compile-time, a compile error is emitted instead.
compile_time,
};
};
{#code_end#}

{#header_open|Calling with a New Stack#}
<p>
When the {#syntax#}stack{#endsyntax#} option is provided, instead of using the same stack as the caller, the function uses the provided stack.
</p>
{#code_begin|test|new_stack_call#}
const std = @import("std");
const assert = std.debug.assert;

var new_stack_bytes: [1024]u8 align(16) = undefined;

test "calling a function with a new stack" {
const arg = 1234;

const a = @call(.{.stack = new_stack_bytes[0..512]}, targetFunction, .{arg});
const b = @call(.{.stack = new_stack_bytes[512..]}, targetFunction, .{arg});
_ = targetFunction(arg);

assert(arg == 1234);
assert(a < b);
}

fn targetFunction(x: i32) usize {
assert(x == 1234);

var local_variable: i32 = 42;
const ptr = &local_variable;
ptr.* += 1;

assert(local_variable == 43);
return @ptrToInt(ptr);
}
{#code_end#}
{#header_close#}
{#header_close#}

{#header_open|@cDefine#}
<pre>{#syntax#}@cDefine(comptime name: []u8, value){#endsyntax#}</pre>
<p>
Expand Down Expand Up @@ -7427,27 +7520,6 @@ test "@hasDecl" {
{#see_also|Compile Variables|@embedFile#}
{#header_close#}

{#header_open|@inlineCall#}
<pre>{#syntax#}@inlineCall(function: X, args: ...) Y{#endsyntax#}</pre>
<p>
This calls a function, in the same way that invoking an expression with parentheses does:
</p>
{#code_begin|test#}
const assert = @import("std").debug.assert;

test "inline function call" {
assert(@inlineCall(add, 3, 9) == 12);
}

fn add(a: i32, b: i32) i32 { return a + b; }
{#code_end#}
<p>
Unlike a normal function call, however, {#syntax#}@inlineCall{#endsyntax#} guarantees that the call
will be inlined. If the call cannot be inlined, a compile error is emitted.
</p>
{#see_also|@noInlineCall#}
{#header_close#}

{#header_open|@intCast#}
<pre>{#syntax#}@intCast(comptime DestType: type, int: var) DestType{#endsyntax#}</pre>
<p>
Expand Down Expand Up @@ -7605,71 +7677,6 @@ mem.set(u8, dest, c);{#endsyntax#}</pre>
</p>
{#header_close#}

{#header_open|@newStackCall#}
<pre>{#syntax#}@newStackCall(new_stack: []align(target_stack_align) u8, function: var, args: ...) var{#endsyntax#}</pre>
<p>
This calls a function, in the same way that invoking an expression with parentheses does. However,
instead of using the same stack as the caller, the function uses the stack provided in the {#syntax#}new_stack{#endsyntax#}
parameter.
</p>
<p>
The new stack must be aligned to {#syntax#}target_stack_align{#endsyntax#} bytes. This is a target-specific
number. A safe value that will work on all targets is {#syntax#}16{#endsyntax#}. This value can
also be obtained by using {#link|@sizeOf#} on the {#link|@Frame#} type of {#link|Async Functions#}.
</p>
{#code_begin|test#}
const std = @import("std");
const assert = std.debug.assert;

var new_stack_bytes: [1024]u8 align(16) = undefined;

test "calling a function with a new stack" {
const arg = 1234;

const a = @newStackCall(new_stack_bytes[0..512], targetFunction, arg);
const b = @newStackCall(new_stack_bytes[512..], targetFunction, arg);
_ = targetFunction(arg);

assert(arg == 1234);
assert(a < b);
}

fn targetFunction(x: i32) usize {
assert(x == 1234);

var local_variable: i32 = 42;
const ptr = &local_variable;
ptr.* += 1;

assert(local_variable == 43);
return @ptrToInt(ptr);
}
{#code_end#}
{#header_close#}

{#header_open|@noInlineCall#}
<pre>{#syntax#}@noInlineCall(function: var, args: ...) var{#endsyntax#}</pre>
<p>
This calls a function, in the same way that invoking an expression with parentheses does:
</p>
{#code_begin|test#}
const assert = @import("std").debug.assert;

test "noinline function call" {
assert(@noInlineCall(add, 3, 9) == 12);
}

fn add(a: i32, b: i32) i32 {
return a + b;
}
{#code_end#}
<p>
Unlike a normal function call, however, {#syntax#}@noInlineCall{#endsyntax#} guarantees that the call
will not be inlined. If the call must be inlined, a compile error is emitted.
</p>
{#see_also|@inlineCall#}
{#header_close#}

{#header_open|@OpaqueType#}
<pre>{#syntax#}@OpaqueType() type{#endsyntax#}</pre>
<p>
Expand Down
38 changes: 38 additions & 0 deletions lib/std/builtin.zig
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,44 @@ pub const Version = struct {
patch: u32,
};

/// This data structure is used by the Zig language code generation and
/// therefore must be kept in sync with the compiler implementation.
pub const CallOptions = struct {
modifier: Modifier = .auto,
stack: ?[]align(std.Target.stack_align) u8 = null,

pub const Modifier = enum {
/// Equivalent to function call syntax.
auto,

/// Prevents tail call optimization. This guarantees that the return
/// address will point to the callsite, as opposed to the callsite's
/// callsite. If the call is otherwise required to be tail-called
/// or inlined, a compile error is emitted instead.
never_tail,

/// Guarantees that the call will not be inlined. If the call is
/// otherwise required to be inlined, a compile error is emitted instead.
never_inline,

/// Asserts that the function call will not suspend. This allows a
/// non-async function to call an async function.
no_async,

/// Guarantees that the call will be generated with tail call optimization.
/// If this is not possible, a compile error is emitted instead.
always_tail,

/// Guarantees that the call will inlined at the callsite.
/// If this is not possible, a compile error is emitted instead.
always_inline,

/// Evaluates the call at compile-time. If the call cannot be completed at
/// compile-time, a compile error is emitted instead.
compile_time,
};
};

/// This function type is used by the Zig language code generation and
/// therefore must be kept in sync with the compiler implementation.
pub const PanicFn = fn ([]const u8, ?*StackTrace) noreturn;
Expand Down
4 changes: 2 additions & 2 deletions lib/std/hash/auto_hash.zig
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ pub fn hash(hasher: var, key: var, comptime strat: HashStrategy) void {

// Help the optimizer see that hashing an int is easy by inlining!
// TODO Check if the situation is better after #561 is resolved.
.Int => @inlineCall(hasher.update, std.mem.asBytes(&key)),
.Int => @call(.{ .modifier = .always_inline }, hasher.update, .{std.mem.asBytes(&key)}),

.Float => |info| hash(hasher, @bitCast(@IntType(false, info.bits), key), strat),

Expand All @@ -101,7 +101,7 @@ pub fn hash(hasher: var, key: var, comptime strat: HashStrategy) void {
.ErrorSet => hash(hasher, @errorToInt(key), strat),
.AnyFrame, .Fn => hash(hasher, @ptrToInt(key), strat),

.Pointer => @inlineCall(hashPointer, hasher, key, strat),
.Pointer => @call(.{ .modifier = .always_inline }, hashPointer, .{ hasher, key, strat }),

.Optional => if (key) |k| hash(hasher, k, strat),

Expand Down
15 changes: 11 additions & 4 deletions lib/std/hash/cityhash.zig
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ pub const CityHash64 = struct {
}

fn hashLen16(u: u64, v: u64) u64 {
return @inlineCall(hash128To64, u, v);
return @call(.{ .modifier = .always_inline }, hash128To64, .{ u, v });
}

fn hashLen16Mul(low: u64, high: u64, mul: u64) u64 {
Expand All @@ -210,7 +210,7 @@ pub const CityHash64 = struct {
}

fn hash128To64(low: u64, high: u64) u64 {
return @inlineCall(hashLen16Mul, low, high, 0x9ddfea08eb382d69);
return @call(.{ .modifier = .always_inline }, hashLen16Mul, .{ low, high, 0x9ddfea08eb382d69 });
}

fn hashLen0To16(str: []const u8) u64 {
Expand Down Expand Up @@ -291,7 +291,14 @@ pub const CityHash64 = struct {
}

fn weakHashLen32WithSeeds(ptr: [*]const u8, a: u64, b: u64) WeakPair {
return @inlineCall(weakHashLen32WithSeedsHelper, fetch64(ptr), fetch64(ptr + 8), fetch64(ptr + 16), fetch64(ptr + 24), a, b);
return @call(.{ .modifier = .always_inline }, weakHashLen32WithSeedsHelper, .{
fetch64(ptr),
fetch64(ptr + 8),
fetch64(ptr + 16),
fetch64(ptr + 24),
a,
b,
});
}

pub fn hash(str: []const u8) u64 {
Expand Down Expand Up @@ -339,7 +346,7 @@ pub const CityHash64 = struct {
}

pub fn hashWithSeed(str: []const u8, seed: u64) u64 {
return @inlineCall(Self.hashWithSeeds, str, k2, seed);
return @call(.{ .modifier = .always_inline }, Self.hashWithSeeds, .{ str, k2, seed });
}

pub fn hashWithSeeds(str: []const u8, seed0: u64, seed1: u64) u64 {
Expand Down
18 changes: 9 additions & 9 deletions lib/std/hash/murmur.zig
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ pub const Murmur2_32 = struct {
const Self = @This();

pub fn hash(str: []const u8) u32 {
return @inlineCall(Self.hashWithSeed, str, default_seed);
return @call(.{ .modifier = .always_inline }, Self.hashWithSeed, .{ str, default_seed });
}

pub fn hashWithSeed(str: []const u8, seed: u32) u32 {
Expand Down Expand Up @@ -44,7 +44,7 @@ pub const Murmur2_32 = struct {
}

pub fn hashUint32(v: u32) u32 {
return @inlineCall(Self.hashUint32WithSeed, v, default_seed);
return @call(.{ .modifier = .always_inline }, Self.hashUint32WithSeed, .{ v, default_seed });
}

pub fn hashUint32WithSeed(v: u32, seed: u32) u32 {
Expand All @@ -64,7 +64,7 @@ pub const Murmur2_32 = struct {
}

pub fn hashUint64(v: u64) u32 {
return @inlineCall(Self.hashUint64WithSeed, v, default_seed);
return @call(.{ .modifier = .always_inline }, Self.hashUint64WithSeed, .{ v, default_seed });
}

pub fn hashUint64WithSeed(v: u64, seed: u32) u32 {
Expand Down Expand Up @@ -93,7 +93,7 @@ pub const Murmur2_64 = struct {
const Self = @This();

pub fn hash(str: []const u8) u64 {
return @inlineCall(Self.hashWithSeed, str, default_seed);
return @call(.{ .modifier = .always_inline }, Self.hashWithSeed, .{ str, default_seed });
}

pub fn hashWithSeed(str: []const u8, seed: u64) u64 {
Expand Down Expand Up @@ -127,7 +127,7 @@ pub const Murmur2_64 = struct {
}

pub fn hashUint32(v: u32) u64 {
return @inlineCall(Self.hashUint32WithSeed, v, default_seed);
return @call(.{ .modifier = .always_inline }, Self.hashUint32WithSeed, .{ v, default_seed });
}

pub fn hashUint32WithSeed(v: u32, seed: u32) u64 {
Expand All @@ -144,7 +144,7 @@ pub const Murmur2_64 = struct {
}

pub fn hashUint64(v: u64) u64 {
return @inlineCall(Self.hashUint64WithSeed, v, default_seed);
return @call(.{ .modifier = .always_inline }, Self.hashUint64WithSeed, .{ v, default_seed });
}

pub fn hashUint64WithSeed(v: u64, seed: u32) u64 {
Expand Down Expand Up @@ -172,7 +172,7 @@ pub const Murmur3_32 = struct {
}

pub fn hash(str: []const u8) u32 {
return @inlineCall(Self.hashWithSeed, str, default_seed);
return @call(.{ .modifier = .always_inline }, Self.hashWithSeed, .{ str, default_seed });
}

pub fn hashWithSeed(str: []const u8, seed: u32) u32 {
Expand Down Expand Up @@ -220,7 +220,7 @@ pub const Murmur3_32 = struct {
}

pub fn hashUint32(v: u32) u32 {
return @inlineCall(Self.hashUint32WithSeed, v, default_seed);
return @call(.{ .modifier = .always_inline }, Self.hashUint32WithSeed, .{ v, default_seed });
}

pub fn hashUint32WithSeed(v: u32, seed: u32) u32 {
Expand All @@ -246,7 +246,7 @@ pub const Murmur3_32 = struct {
}

pub fn hashUint64(v: u64) u32 {
return @inlineCall(Self.hashUint64WithSeed, v, default_seed);
return @call(.{ .modifier = .always_inline }, Self.hashUint64WithSeed, .{ v, default_seed });
}

pub fn hashUint64WithSeed(v: u64, seed: u32) u32 {
Expand Down
Loading