stage2: fix generics with non-comptime anytype parameters

The `comptime_args` field of Fn has a clarified purpose:
For generic function instantiations, there is a `TypedValue` here
for each parameter of the function:
 * Non-comptime parameters are marked with a `generic_poison` for the value.
 * Non-anytype parameters are marked with a `generic_poison` for the type.

Sema now has a `fn_ret_ty` field. Doc comments reproduced here:
> When semantic analysis needs to know the return type of the function whose body
> is being analyzed, this `Type` should be used instead of going through `func`.
> This will correctly handle the case of a comptime/inline function call of a
> generic function which uses a type expression for the return type.
> The type will be `void` in the case that `func` is `null`.
Various places in Sema are modified in accordance with this guidance.

Fixed `resolveMaybeUndefVal` not returning `error.GenericPoison` when
Value Tag of `generic_poison` is encountered.

Fixed generic function memoization incorrect equality checking. The
logic now clearly deals properly with any combination of anytype and
comptime parameters.

Fixed not removing generic function instantiation from the table in case
a compile errors in the rest of `call` semantic analysis. This required
introduction of yet another adapter which I have called
`GenericRemoveAdapter`. This one is nice and simple - it's the same hash
function (the same precomputed hash is passed in) but the equality
function checks pointers rather than doing any logic.

Inline/comptime function calls coerce each argument in accordance with
the function parameter type expressions. Likewise the return type
expression is evaluated and provided (see `fn_ret_ty` above).

There's a new compile error "unable to monomorphize function". It's
pretty unhelpful and will need to get improved in the future. It happens
when a type expression in a generic function did not end up getting
resolved at a callsite. This can happen, for example, if a runtime
parameter is attempted to be used where it needed to be comptime known:

```zig
fn foo(x: anytype) [x]u8 { _ = x; }
```

In this example, even if we pass a number such as `10` for `x`, it is
not marked `comptime`, so `x` will have a runtime known value, making
the return type unable to resolve.

In the LLVM backend I implement cmp instructions for float types to pass
some behavior tests that used floats.
This commit is contained in:
Andrew Kelley 2021-08-06 16:24:39 -07:00
parent ea7bdeb67d
commit ede76f4fe3
6 changed files with 301 additions and 149 deletions

View File

@ -801,8 +801,9 @@ pub const Fn = struct {
/// The Decl that corresponds to the function itself. /// The Decl that corresponds to the function itself.
owner_decl: *Decl, owner_decl: *Decl,
/// If this is not null, this function is a generic function instantiation, and /// If this is not null, this function is a generic function instantiation, and
/// there is a `Value` here for each parameter of the function. Non-comptime /// there is a `TypedValue` here for each parameter of the function.
/// parameters are marked with an `unreachable_value`. /// Non-comptime parameters are marked with a `generic_poison` for the value.
/// Non-anytype parameters are marked with a `generic_poison` for the type.
comptime_args: ?[*]TypedValue = null, comptime_args: ?[*]TypedValue = null,
/// The ZIR instruction that is a function instruction. Use this to find /// The ZIR instruction that is a function instruction. Use this to find
/// the body. We store this rather than the body directly so that when ZIR /// the body. We store this rather than the body directly so that when ZIR
@ -2975,6 +2976,7 @@ pub fn semaFile(mod: *Module, file: *Scope.File) SemaError!void {
.owner_decl = new_decl, .owner_decl = new_decl,
.namespace = &struct_obj.namespace, .namespace = &struct_obj.namespace,
.func = null, .func = null,
.fn_ret_ty = Type.initTag(.void),
.owner_func = null, .owner_func = null,
}; };
defer sema.deinit(); defer sema.deinit();
@ -3029,6 +3031,7 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool {
.owner_decl = decl, .owner_decl = decl,
.namespace = decl.namespace, .namespace = decl.namespace,
.func = null, .func = null,
.fn_ret_ty = Type.initTag(.void),
.owner_func = null, .owner_func = null,
}; };
defer sema.deinit(); defer sema.deinit();
@ -3712,6 +3715,7 @@ pub fn analyzeFnBody(mod: *Module, decl: *Decl, func: *Fn) SemaError!Air {
.owner_decl = decl, .owner_decl = decl,
.namespace = decl.namespace, .namespace = decl.namespace,
.func = func, .func = func,
.fn_ret_ty = func.owner_decl.ty.fnReturnType(),
.owner_func = func, .owner_func = func,
}; };
defer sema.deinit(); defer sema.deinit();
@ -3764,7 +3768,7 @@ pub fn analyzeFnBody(mod: *Module, decl: *Decl, func: *Fn) SemaError!Air {
}; };
if (func.comptime_args) |comptime_args| { if (func.comptime_args) |comptime_args| {
const arg_tv = comptime_args[total_param_index]; const arg_tv = comptime_args[total_param_index];
if (arg_tv.val.tag() != .unreachable_value) { if (arg_tv.val.tag() != .generic_poison) {
// We have a comptime value for this parameter. // We have a comptime value for this parameter.
const arg = try sema.addConstant(arg_tv.ty, arg_tv.val); const arg = try sema.addConstant(arg_tv.ty, arg_tv.val);
sema.inst_map.putAssumeCapacityNoClobber(inst, arg); sema.inst_map.putAssumeCapacityNoClobber(inst, arg);
@ -4447,6 +4451,7 @@ pub fn analyzeStructFields(mod: *Module, struct_obj: *Struct) CompileError!void
.namespace = &struct_obj.namespace, .namespace = &struct_obj.namespace,
.owner_func = null, .owner_func = null,
.func = null, .func = null,
.fn_ret_ty = Type.initTag(.void),
}; };
defer sema.deinit(); defer sema.deinit();
@ -4600,6 +4605,7 @@ pub fn analyzeUnionFields(mod: *Module, union_obj: *Union) CompileError!void {
.namespace = &union_obj.namespace, .namespace = &union_obj.namespace,
.owner_func = null, .owner_func = null,
.func = null, .func = null,
.fn_ret_ty = Type.initTag(.void),
}; };
defer sema.deinit(); defer sema.deinit();

View File

@ -29,6 +29,12 @@ owner_func: ?*Module.Fn,
/// This starts out the same as `owner_func` and then diverges in the case of /// This starts out the same as `owner_func` and then diverges in the case of
/// an inline or comptime function call. /// an inline or comptime function call.
func: ?*Module.Fn, func: ?*Module.Fn,
/// When semantic analysis needs to know the return type of the function whose body
/// is being analyzed, this `Type` should be used instead of going through `func`.
/// This will correctly handle the case of a comptime/inline function call of a
/// generic function which uses a type expression for the return type.
/// The type will be `void` in the case that `func` is `null`.
fn_ret_ty: Type,
branch_quota: u32 = 1000, branch_quota: u32 = 1000,
branch_count: u32 = 0, branch_count: u32 = 0,
/// This field is updated when a new source location becomes active, so that /// This field is updated when a new source location becomes active, so that
@ -628,6 +634,7 @@ fn analyzeAsType(
/// May return Value Tags: `variable`, `undef`. /// May return Value Tags: `variable`, `undef`.
/// See `resolveConstValue` for an alternative. /// See `resolveConstValue` for an alternative.
/// Value Tag `generic_poison` causes `error.GenericPoison` to be returned.
fn resolveValue( fn resolveValue(
sema: *Sema, sema: *Sema,
block: *Scope.Block, block: *Scope.Block,
@ -679,6 +686,7 @@ fn resolveDefinedValue(
/// Value Tag `variable` causes this function to return `null`. /// Value Tag `variable` causes this function to return `null`.
/// Value Tag `undef` causes this function to return the Value. /// Value Tag `undef` causes this function to return the Value.
/// Value Tag `generic_poison` causes `error.GenericPoison` to be returned.
fn resolveMaybeUndefVal( fn resolveMaybeUndefVal(
sema: *Sema, sema: *Sema,
block: *Scope.Block, block: *Scope.Block,
@ -686,10 +694,11 @@ fn resolveMaybeUndefVal(
inst: Air.Inst.Ref, inst: Air.Inst.Ref,
) CompileError!?Value { ) CompileError!?Value {
const val = (try sema.resolveMaybeUndefValAllowVariables(block, src, inst)) orelse return null; const val = (try sema.resolveMaybeUndefValAllowVariables(block, src, inst)) orelse return null;
if (val.tag() == .variable) { switch (val.tag()) {
return null; .variable => return null,
.generic_poison => return error.GenericPoison,
else => return val,
} }
return val;
} }
/// Returns all Value tags including `variable` and `undef`. /// Returns all Value tags including `variable` and `undef`.
@ -1033,6 +1042,7 @@ fn zirEnumDecl(
.namespace = &enum_obj.namespace, .namespace = &enum_obj.namespace,
.owner_func = null, .owner_func = null,
.func = null, .func = null,
.fn_ret_ty = Type.initTag(.void),
.branch_quota = sema.branch_quota, .branch_quota = sema.branch_quota,
.branch_count = sema.branch_count, .branch_count = sema.branch_count,
}; };
@ -1238,9 +1248,7 @@ fn zirRetPtr(
const src: LazySrcLoc = .{ .node_offset = @bitCast(i32, extended.operand) }; const src: LazySrcLoc = .{ .node_offset = @bitCast(i32, extended.operand) };
try sema.requireFunctionBlock(block, src); try sema.requireFunctionBlock(block, src);
const fn_ty = sema.func.?.owner_decl.ty; const ptr_type = try Module.simplePtrType(sema.arena, sema.fn_ret_ty, true, .One);
const ret_type = fn_ty.fnReturnType();
const ptr_type = try Module.simplePtrType(sema.arena, ret_type, true, .One);
return block.addTy(.alloc, ptr_type); return block.addTy(.alloc, ptr_type);
} }
@ -1263,9 +1271,7 @@ fn zirRetType(
const src: LazySrcLoc = .{ .node_offset = @bitCast(i32, extended.operand) }; const src: LazySrcLoc = .{ .node_offset = @bitCast(i32, extended.operand) };
try sema.requireFunctionBlock(block, src); try sema.requireFunctionBlock(block, src);
const fn_ty = sema.func.?.owner_decl.ty; return sema.addType(sema.fn_ret_ty);
const ret_type = fn_ty.fnReturnType();
return sema.addType(ret_type);
} }
fn zirEnsureResultUsed(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!void { fn zirEnsureResultUsed(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!void {
@ -2364,7 +2370,7 @@ const GenericCallAdapter = struct {
generic_fn: *Module.Fn, generic_fn: *Module.Fn,
precomputed_hash: u64, precomputed_hash: u64,
func_ty_info: Type.Payload.Function.Data, func_ty_info: Type.Payload.Function.Data,
comptime_vals: []const Value, comptime_tvs: []const TypedValue,
pub fn eql(ctx: @This(), adapted_key: void, other_key: *Module.Fn) bool { pub fn eql(ctx: @This(), adapted_key: void, other_key: *Module.Fn) bool {
_ = adapted_key; _ = adapted_key;
@ -2373,12 +2379,22 @@ const GenericCallAdapter = struct {
const generic_owner_decl = other_key.owner_decl.dependencies.keys()[0]; const generic_owner_decl = other_key.owner_decl.dependencies.keys()[0];
if (ctx.generic_fn.owner_decl != generic_owner_decl) return false; if (ctx.generic_fn.owner_decl != generic_owner_decl) return false;
// This logic must be kept in sync with the logic in `analyzeCall` that
// computes the hash.
const other_comptime_args = other_key.comptime_args.?; const other_comptime_args = other_key.comptime_args.?;
for (ctx.func_ty_info.param_types) |param_ty, i| { for (other_comptime_args[0..ctx.func_ty_info.param_types.len]) |other_arg, i| {
if (ctx.func_ty_info.paramIsComptime(i) and param_ty.tag() != .generic_poison) { if (other_arg.ty.tag() != .generic_poison) {
if (!ctx.comptime_vals[i].eql(other_comptime_args[i].val, param_ty)) { // anytype parameter
if (!other_arg.ty.eql(ctx.comptime_tvs[i].ty)) {
return false;
}
}
if (other_arg.val.tag() != .generic_poison) {
// comptime parameter
if (ctx.comptime_tvs[i].val.tag() == .generic_poison) {
// No match because the instantiation has a comptime parameter
// but the callsite does not.
return false;
}
if (!other_arg.val.eql(ctx.comptime_tvs[i].val, other_arg.ty)) {
return false; return false;
} }
} }
@ -2394,6 +2410,22 @@ const GenericCallAdapter = struct {
} }
}; };
const GenericRemoveAdapter = struct {
precomputed_hash: u64,
pub fn eql(ctx: @This(), adapted_key: *Module.Fn, other_key: *Module.Fn) bool {
_ = ctx;
return adapted_key == other_key;
}
/// The implementation of the hash is in semantic analysis of function calls, so
/// that any errors when computing the hash can be properly reported.
pub fn hash(ctx: @This(), adapted_key: *Module.Fn) u64 {
_ = adapted_key;
return ctx.precomputed_hash;
}
};
fn analyzeCall( fn analyzeCall(
sema: *Sema, sema: *Sema,
block: *Scope.Block, block: *Scope.Block,
@ -2466,14 +2498,6 @@ fn analyzeCall(
const is_inline_call = is_comptime_call or modifier == .always_inline or const is_inline_call = is_comptime_call or modifier == .always_inline or
func_ty_info.cc == .Inline; func_ty_info.cc == .Inline;
const result: Air.Inst.Ref = if (is_inline_call) res: { const result: Air.Inst.Ref = if (is_inline_call) res: {
// TODO look into not allocating this args array
const args = try sema.arena.alloc(Air.Inst.Ref, uncasted_args.len);
for (uncasted_args) |uncasted_arg, i| {
const param_ty = func_ty.fnParamType(i);
const arg_src = call_src; // TODO: better source location
args[i] = try sema.coerce(block, param_ty, uncasted_arg, arg_src);
}
const func_val = try sema.resolveConstValue(block, func_src, func); const func_val = try sema.resolveConstValue(block, func_src, func);
const module_fn = switch (func_val.tag()) { const module_fn = switch (func_val.tag()) {
.function => func_val.castTag(.function).?.data, .function => func_val.castTag(.function).?.data,
@ -2544,19 +2568,62 @@ fn analyzeCall(
// This will have return instructions analyzed as break instructions to // This will have return instructions analyzed as break instructions to
// the block_inst above. Here we are performing "comptime/inline semantic analysis" // the block_inst above. Here we are performing "comptime/inline semantic analysis"
// for a function body, which means we must map the parameter ZIR instructions to // for a function body, which means we must map the parameter ZIR instructions to
// the AIR instructions of the callsite. // the AIR instructions of the callsite. The callee could be a generic function
// which means its parameter type expressions must be resolved in order and used
// to successively coerce the arguments.
const fn_info = sema.code.getFnInfo(module_fn.zir_body_inst); const fn_info = sema.code.getFnInfo(module_fn.zir_body_inst);
const zir_tags = sema.code.instructions.items(.tag); const zir_tags = sema.code.instructions.items(.tag);
var arg_i: usize = 0; var arg_i: usize = 0;
try sema.inst_map.ensureUnusedCapacity(gpa, @intCast(u32, args.len)); for (fn_info.param_body) |inst| switch (zir_tags[inst]) {
for (fn_info.param_body) |inst| { .param, .param_comptime => {
switch (zir_tags[inst]) { // Evaluate the parameter type expression now that previous ones have
.param, .param_comptime, .param_anytype, .param_anytype_comptime => {}, // been mapped, and coerce the corresponding argument to it.
else => continue, const pl_tok = sema.code.instructions.items(.data)[inst].pl_tok;
const param_src = pl_tok.src();
const extra = sema.code.extraData(Zir.Inst.Param, pl_tok.payload_index);
const param_body = sema.code.extra[extra.end..][0..extra.data.body_len];
const param_ty_inst = try sema.resolveBody(&child_block, param_body);
const param_ty = try sema.analyzeAsType(&child_block, param_src, param_ty_inst);
const arg_src = call_src; // TODO: better source location
const casted_arg = try sema.coerce(&child_block, param_ty, uncasted_args[arg_i], arg_src);
try sema.inst_map.putNoClobber(gpa, inst, casted_arg);
arg_i += 1;
continue;
},
.param_anytype, .param_anytype_comptime => {
// No coercion needed.
try sema.inst_map.putNoClobber(gpa, inst, uncasted_args[arg_i]);
arg_i += 1;
continue;
},
else => continue,
};
// In case it is a generic function with an expression for the return type that depends
// on parameters, we must now do the same for the return type as we just did with
// each of the parameters, resolving the return type and providing it to the child
// `Sema` so that it can be used for the `ret_ptr` instruction.
const ret_ty_inst = try sema.resolveBody(&child_block, fn_info.ret_ty_body);
const ret_ty_src = func_src; // TODO better source location
const bare_return_type = try sema.analyzeAsType(&child_block, ret_ty_src, ret_ty_inst);
// If the function has an inferred error set, `bare_return_type` is the payload type only.
const fn_ret_ty = blk: {
// TODO instead of reusing the function's inferred error set, this code should
// create a temporary error set which is used for the comptime/inline function
// call alone, independent from the runtime instantiation.
if (func_ty_info.return_type.castTag(.error_union)) |payload| {
const error_set_ty = payload.data.error_set;
break :blk try Type.Tag.error_union.create(sema.arena, .{
.error_set = error_set_ty,
.payload = bare_return_type,
});
} }
sema.inst_map.putAssumeCapacityNoClobber(inst, args[arg_i]); break :blk bare_return_type;
arg_i += 1; };
} const parent_fn_ret_ty = sema.fn_ret_ty;
sema.fn_ret_ty = fn_ret_ty;
defer sema.fn_ret_ty = parent_fn_ret_ty;
_ = try sema.analyzeBody(&child_block, fn_info.body); _ = try sema.analyzeBody(&child_block, fn_info.body);
break :res try sema.analyzeBlockBody(block, call_src, &child_block, merges); break :res try sema.analyzeBlockBody(block, call_src, &child_block, merges);
} else if (func_ty_info.is_generic) res: { } else if (func_ty_info.is_generic) res: {
@ -2569,57 +2636,74 @@ fn analyzeCall(
const fn_zir = namespace.file_scope.zir; const fn_zir = namespace.file_scope.zir;
const fn_info = fn_zir.getFnInfo(module_fn.zir_body_inst); const fn_info = fn_zir.getFnInfo(module_fn.zir_body_inst);
const zir_tags = fn_zir.instructions.items(.tag); const zir_tags = fn_zir.instructions.items(.tag);
const new_module_func = new_func: {
// This hash must match `Module.MonomorphedFuncsContext.hash`.
// For parameters explicitly marked comptime and simple parameter type expressions,
// we know whether a parameter is elided from a monomorphed function, and can
// use it in the hash here. However, for parameter type expressions that are not
// explicitly marked comptime and rely on previous parameter comptime values, we
// don't find out until after generating a monomorphed function whether the parameter
// type ended up being a "must-be-comptime-known" type.
var hasher = std.hash.Wyhash.init(0);
std.hash.autoHash(&hasher, @ptrToInt(module_fn));
const comptime_vals = try sema.arena.alloc(Value, func_ty_info.param_types.len); // This hash must match `Module.MonomorphedFuncsContext.hash`.
// For parameters explicitly marked comptime and simple parameter type expressions,
// we know whether a parameter is elided from a monomorphed function, and can
// use it in the hash here. However, for parameter type expressions that are not
// explicitly marked comptime and rely on previous parameter comptime values, we
// don't find out until after generating a monomorphed function whether the parameter
// type ended up being a "must-be-comptime-known" type.
var hasher = std.hash.Wyhash.init(0);
std.hash.autoHash(&hasher, @ptrToInt(module_fn));
for (func_ty_info.param_types) |param_ty, i| { const comptime_tvs = try sema.arena.alloc(TypedValue, func_ty_info.param_types.len);
const is_comptime = func_ty_info.paramIsComptime(i);
if (is_comptime and param_ty.tag() != .generic_poison) { for (func_ty_info.param_types) |param_ty, i| {
const arg_src = call_src; // TODO better source location const is_comptime = func_ty_info.paramIsComptime(i);
const casted_arg = try sema.coerce(block, param_ty, uncasted_args[i], arg_src); if (is_comptime) {
if (try sema.resolveMaybeUndefVal(block, arg_src, casted_arg)) |arg_val| { const arg_src = call_src; // TODO better source location
const casted_arg = try sema.coerce(block, param_ty, uncasted_args[i], arg_src);
if (try sema.resolveMaybeUndefVal(block, arg_src, casted_arg)) |arg_val| {
if (param_ty.tag() != .generic_poison) {
arg_val.hash(param_ty, &hasher); arg_val.hash(param_ty, &hasher);
comptime_vals[i] = arg_val;
} else {
return sema.failWithNeededComptime(block, arg_src);
} }
comptime_tvs[i] = .{
// This will be different than `param_ty` in the case of `generic_poison`.
.ty = sema.typeOf(casted_arg),
.val = arg_val,
};
} else {
return sema.failWithNeededComptime(block, arg_src);
} }
} else {
comptime_tvs[i] = .{
.ty = sema.typeOf(uncasted_args[i]),
.val = Value.initTag(.generic_poison),
};
} }
}
const adapter: GenericCallAdapter = .{ const precomputed_hash = hasher.final();
.generic_fn = module_fn,
.precomputed_hash = hasher.final(), const adapter: GenericCallAdapter = .{
.func_ty_info = func_ty_info, .generic_fn = module_fn,
.comptime_vals = comptime_vals, .precomputed_hash = precomputed_hash,
}; .func_ty_info = func_ty_info,
const gop = try mod.monomorphed_funcs.getOrPutAdapted(gpa, {}, adapter); .comptime_tvs = comptime_tvs,
if (gop.found_existing) {
const callee_func = gop.key_ptr.*;
break :res try sema.finishGenericCall(
block,
call_src,
callee_func,
func_src,
uncasted_args,
fn_info,
zir_tags,
);
}
gop.key_ptr.* = try gpa.create(Module.Fn);
break :new_func gop.key_ptr.*;
}; };
const gop = try mod.monomorphed_funcs.getOrPutAdapted(gpa, {}, adapter);
if (gop.found_existing) {
const callee_func = gop.key_ptr.*;
break :res try sema.finishGenericCall(
block,
call_src,
callee_func,
func_src,
uncasted_args,
fn_info,
zir_tags,
);
}
const new_module_func = try gpa.create(Module.Fn);
gop.key_ptr.* = new_module_func;
{ {
errdefer gpa.destroy(new_module_func);
const remove_adapter: GenericRemoveAdapter = .{
.precomputed_hash = precomputed_hash,
};
errdefer assert(mod.monomorphed_funcs.removeAdapted(new_module_func, remove_adapter));
try namespace.anon_decls.ensureUnusedCapacity(gpa, 1); try namespace.anon_decls.ensureUnusedCapacity(gpa, 1);
// Create a Decl for the new function. // Create a Decl for the new function.
@ -2658,6 +2742,7 @@ fn analyzeCall(
.owner_decl = new_decl, .owner_decl = new_decl,
.namespace = namespace, .namespace = namespace,
.func = null, .func = null,
.fn_ret_ty = Type.initTag(.void),
.owner_func = null, .owner_func = null,
.comptime_args = try new_decl_arena.allocator.alloc(TypedValue, uncasted_args.len), .comptime_args = try new_decl_arena.allocator.alloc(TypedValue, uncasted_args.len),
.comptime_args_fn_inst = module_fn.zir_body_inst, .comptime_args_fn_inst = module_fn.zir_body_inst,
@ -2681,11 +2766,25 @@ fn analyzeCall(
try child_sema.inst_map.ensureUnusedCapacity(gpa, @intCast(u32, uncasted_args.len)); try child_sema.inst_map.ensureUnusedCapacity(gpa, @intCast(u32, uncasted_args.len));
var arg_i: usize = 0; var arg_i: usize = 0;
for (fn_info.param_body) |inst| { for (fn_info.param_body) |inst| {
const is_comptime = switch (zir_tags[inst]) { var is_comptime = false;
.param_comptime, .param_anytype_comptime => true, var is_anytype = false;
.param, .param_anytype => false, switch (zir_tags[inst]) {
.param => {
is_comptime = func_ty_info.paramIsComptime(arg_i);
},
.param_comptime => {
is_comptime = true;
},
.param_anytype => {
is_anytype = true;
is_comptime = func_ty_info.paramIsComptime(arg_i);
},
.param_anytype_comptime => {
is_anytype = true;
is_comptime = true;
},
else => continue, else => continue,
} or func_ty_info.paramIsComptime(arg_i); }
const arg_src = call_src; // TODO: better source location const arg_src = call_src; // TODO: better source location
const arg = uncasted_args[arg_i]; const arg = uncasted_args[arg_i];
if (try sema.resolveMaybeUndefVal(block, arg_src, arg)) |arg_val| { if (try sema.resolveMaybeUndefVal(block, arg_src, arg)) |arg_val| {
@ -2693,6 +2792,12 @@ fn analyzeCall(
child_sema.inst_map.putAssumeCapacityNoClobber(inst, child_arg); child_sema.inst_map.putAssumeCapacityNoClobber(inst, child_arg);
} else if (is_comptime) { } else if (is_comptime) {
return sema.failWithNeededComptime(block, arg_src); return sema.failWithNeededComptime(block, arg_src);
} else if (is_anytype) {
const child_arg = try child_sema.addConstant(
sema.typeOf(arg),
Value.initTag(.generic_poison),
);
child_sema.inst_map.putAssumeCapacityNoClobber(inst, child_arg);
} }
arg_i += 1; arg_i += 1;
} }
@ -2710,17 +2815,10 @@ fn analyzeCall(
const arg = child_sema.inst_map.get(inst).?; const arg = child_sema.inst_map.get(inst).?;
const arg_val = (child_sema.resolveMaybeUndefValAllowVariables(&child_block, .unneeded, arg) catch unreachable).?; const arg_val = (child_sema.resolveMaybeUndefValAllowVariables(&child_block, .unneeded, arg) catch unreachable).?;
if (arg_val.tag() == .generic_poison) { child_sema.comptime_args[arg_i] = .{
child_sema.comptime_args[arg_i] = .{ .ty = try child_sema.typeOf(arg).copy(&new_decl_arena.allocator),
.ty = Type.initTag(.noreturn), .val = try arg_val.copy(&new_decl_arena.allocator),
.val = Value.initTag(.unreachable_value), };
};
} else {
child_sema.comptime_args[arg_i] = .{
.ty = try child_sema.typeOf(arg).copy(&new_decl_arena.allocator),
.val = try arg_val.copy(&new_decl_arena.allocator),
};
}
arg_i += 1; arg_i += 1;
} }
@ -2730,6 +2828,18 @@ fn analyzeCall(
new_decl.val = try Value.Tag.function.create(&new_decl_arena.allocator, new_func); new_decl.val = try Value.Tag.function.create(&new_decl_arena.allocator, new_func);
new_decl.analysis = .complete; new_decl.analysis = .complete;
if (new_decl.ty.fnInfo().is_generic) {
// TODO improve this error message. This can happen because of the parameter
// type expression or return type expression depending on runtime-provided values.
// The error message should be emitted in zirParam or funcCommon when it
// is determined that we are trying to instantiate a generic function.
return mod.fail(&block.base, call_src, "unable to monomorphize function", .{});
}
log.debug("generic function '{s}' instantiated with type {}", .{
new_decl.name, new_decl.ty,
});
// The generic function Decl is guaranteed to be the first dependency // The generic function Decl is guaranteed to be the first dependency
// of each of its instantiations. // of each of its instantiations.
assert(new_decl.dependencies.keys().len == 0); assert(new_decl.dependencies.keys().len == 0);
@ -2809,7 +2919,7 @@ fn finishGenericCall(
for (fn_info.param_body) |inst| { for (fn_info.param_body) |inst| {
switch (zir_tags[inst]) { switch (zir_tags[inst]) {
.param_comptime, .param_anytype_comptime, .param, .param_anytype => { .param_comptime, .param_anytype_comptime, .param, .param_anytype => {
if (comptime_args[arg_i].val.tag() == .unreachable_value) { if (comptime_args[arg_i].val.tag() == .generic_poison) {
count += 1; count += 1;
} }
arg_i += 1; arg_i += 1;
@ -2829,7 +2939,7 @@ fn finishGenericCall(
.param_comptime, .param_anytype_comptime, .param, .param_anytype => {}, .param_comptime, .param_anytype_comptime, .param, .param_anytype => {},
else => continue, else => continue,
} }
const is_runtime = comptime_args[total_i].val.tag() == .unreachable_value; const is_runtime = comptime_args[total_i].val.tag() == .generic_poison;
if (is_runtime) { if (is_runtime) {
const param_ty = new_fn_ty.fnParamType(runtime_i); const param_ty = new_fn_ty.fnParamType(runtime_i);
const arg_src = call_src; // TODO: better source location const arg_src = call_src; // TODO: better source location
@ -6162,28 +6272,23 @@ fn zirRetNode(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErr
fn analyzeRet( fn analyzeRet(
sema: *Sema, sema: *Sema,
block: *Scope.Block, block: *Scope.Block,
operand: Air.Inst.Ref, uncasted_operand: Air.Inst.Ref,
src: LazySrcLoc, src: LazySrcLoc,
need_coercion: bool, need_coercion: bool,
) CompileError!Zir.Inst.Index { ) CompileError!Zir.Inst.Index {
const casted_operand = if (!need_coercion) operand else op: { const operand = if (!need_coercion)
const func = sema.func.?; uncasted_operand
const fn_ty = func.owner_decl.ty; else
// TODO: In the case of a comptime/inline function call of a generic function, try sema.coerce(block, sema.fn_ret_ty, uncasted_operand, src);
// this needs to be the resolved return type based on the function parameter type
// expressions being evaluated with comptime arguments passed in. Otherwise, this
// ends up being .generic_poison and failing the comptime/inline function call analysis.
const fn_ret_ty = fn_ty.fnReturnType();
break :op try sema.coerce(block, fn_ret_ty, operand, src);
};
if (block.inlining) |inlining| { if (block.inlining) |inlining| {
// We are inlining a function call; rewrite the `ret` as a `break`. // We are inlining a function call; rewrite the `ret` as a `break`.
try inlining.merges.results.append(sema.gpa, casted_operand); try inlining.merges.results.append(sema.gpa, operand);
_ = try block.addBr(inlining.merges.block_inst, casted_operand); _ = try block.addBr(inlining.merges.block_inst, operand);
return always_noreturn; return always_noreturn;
} }
_ = try block.addUnOp(.ret, casted_operand); _ = try block.addUnOp(.ret, operand);
return always_noreturn; return always_noreturn;
} }

View File

@ -1093,21 +1093,32 @@ pub const FuncGen = struct {
const rhs = try self.resolveInst(bin_op.rhs); const rhs = try self.resolveInst(bin_op.rhs);
const inst_ty = self.air.typeOfIndex(inst); const inst_ty = self.air.typeOfIndex(inst);
if (!inst_ty.isInt()) switch (self.air.typeOf(bin_op.lhs).zigTypeTag()) {
if (inst_ty.tag() != .bool) .Int, .Bool, .Pointer => {
return self.todo("implement 'airCmp' for type {}", .{inst_ty}); const is_signed = inst_ty.isSignedInt();
const operation = switch (op) {
const is_signed = inst_ty.isSignedInt(); .eq => .EQ,
const operation = switch (op) { .neq => .NE,
.eq => .EQ, .lt => @as(llvm.IntPredicate, if (is_signed) .SLT else .ULT),
.neq => .NE, .lte => @as(llvm.IntPredicate, if (is_signed) .SLE else .ULE),
.lt => @as(llvm.IntPredicate, if (is_signed) .SLT else .ULT), .gt => @as(llvm.IntPredicate, if (is_signed) .SGT else .UGT),
.lte => @as(llvm.IntPredicate, if (is_signed) .SLE else .ULE), .gte => @as(llvm.IntPredicate, if (is_signed) .SGE else .UGE),
.gt => @as(llvm.IntPredicate, if (is_signed) .SGT else .UGT), };
.gte => @as(llvm.IntPredicate, if (is_signed) .SGE else .UGE), return self.builder.buildICmp(operation, lhs, rhs, "");
}; },
.Float => {
return self.builder.buildICmp(operation, lhs, rhs, ""); const operation: llvm.RealPredicate = switch (op) {
.eq => .OEQ,
.neq => .UNE,
.lt => .OLT,
.lte => .OLE,
.gt => .OGT,
.gte => .OGE,
};
return self.builder.buildFCmp(operation, lhs, rhs, "");
},
else => unreachable,
}
} }
fn airBlock(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { fn airBlock(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {

View File

@ -409,6 +409,9 @@ pub const Builder = opaque {
pub const buildICmp = LLVMBuildICmp; pub const buildICmp = LLVMBuildICmp;
extern fn LLVMBuildICmp(*const Builder, Op: IntPredicate, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value; extern fn LLVMBuildICmp(*const Builder, Op: IntPredicate, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
pub const buildFCmp = LLVMBuildFCmp;
extern fn LLVMBuildFCmp(*const Builder, Op: RealPredicate, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
pub const buildBr = LLVMBuildBr; pub const buildBr = LLVMBuildBr;
extern fn LLVMBuildBr(*const Builder, Dest: *const BasicBlock) *const Value; extern fn LLVMBuildBr(*const Builder, Dest: *const BasicBlock) *const Value;
@ -451,7 +454,7 @@ pub const Builder = opaque {
) *const Value; ) *const Value;
}; };
pub const IntPredicate = enum(c_int) { pub const IntPredicate = enum(c_uint) {
EQ = 32, EQ = 32,
NE = 33, NE = 33,
UGT = 34, UGT = 34,
@ -464,6 +467,23 @@ pub const IntPredicate = enum(c_int) {
SLE = 41, SLE = 41,
}; };
pub const RealPredicate = enum(c_uint) {
OEQ = 1,
OGT = 2,
OGE = 3,
OLT = 4,
OLE = 5,
ONE = 6,
ORD = 7,
UNO = 8,
UEQ = 9,
UGT = 10,
UGE = 11,
ULT = 12,
ULE = 13,
UNE = 14,
};
pub const BasicBlock = opaque { pub const BasicBlock = opaque {
pub const deleteBasicBlock = LLVMDeleteBasicBlock; pub const deleteBasicBlock = LLVMDeleteBasicBlock;
extern fn LLVMDeleteBasicBlock(BB: *const BasicBlock) void; extern fn LLVMDeleteBasicBlock(BB: *const BasicBlock) void;

View File

@ -64,9 +64,41 @@ fn sameButWithFloats(a: f64, b: f64) f64 {
test "fn with comptime args" { test "fn with comptime args" {
try expect(gimmeTheBigOne(1234, 5678) == 5678); try expect(gimmeTheBigOne(1234, 5678) == 5678);
try expect(shouldCallSameInstance(34, 12) == 34); try expect(shouldCallSameInstance(34, 12) == 34);
try expect(sameButWithFloats(0.43, 0.49) == 0.49);
}
test "anytype params" {
try expect(max_i32(12, 34) == 34);
try expect(max_f64(1.2, 3.4) == 3.4);
if (!builtin.zig_is_stage2) { if (!builtin.zig_is_stage2) {
// TODO: stage2 llvm backend needs to use fcmp instead of icmp // TODO: stage2 is incorrectly hitting the following problem:
// probably AIR should just have different instructions for floats. // error: unable to resolve comptime value
try expect(sameButWithFloats(0.43, 0.49) == 0.49); // return max_anytype(a, b);
// ^
comptime {
try expect(max_i32(12, 34) == 34);
try expect(max_f64(1.2, 3.4) == 3.4);
}
} }
} }
fn max_anytype(a: anytype, b: anytype) @TypeOf(a, b) {
if (!builtin.zig_is_stage2) {
// TODO: stage2 is incorrectly emitting AIR that allocates a result
// value, stores to it, but then returns void instead of the result.
return if (a > b) a else b;
}
if (a > b) {
return a;
} else {
return b;
}
}
fn max_i32(a: i32, b: i32) i32 {
return max_anytype(a, b);
}
fn max_f64(a: f64, b: f64) f64 {
return max_anytype(a, b);
}

View File

@ -3,28 +3,6 @@ const testing = std.testing;
const expect = testing.expect; const expect = testing.expect;
const expectEqual = testing.expectEqual; const expectEqual = testing.expectEqual;
test "anytype params" {
try expect(max_i32(12, 34) == 34);
try expect(max_f64(1.2, 3.4) == 3.4);
}
test {
comptime try expect(max_i32(12, 34) == 34);
comptime try expect(max_f64(1.2, 3.4) == 3.4);
}
fn max_anytype(a: anytype, b: anytype) @TypeOf(a + b) {
return if (a > b) a else b;
}
fn max_i32(a: i32, b: i32) i32 {
return max_anytype(a, b);
}
fn max_f64(a: f64, b: f64) f64 {
return max_anytype(a, b);
}
pub fn List(comptime T: type) type { pub fn List(comptime T: type) type {
return SmallList(T, 8); return SmallList(T, 8);
} }