diff --git a/src/check_decl.cpp b/src/check_decl.cpp index 60da85d0e..4dff7a838 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -293,13 +293,16 @@ void check_proc_decl(Checker *c, Entity *e, DeclInfo *d) { } if (is_foreign && is_export) { - error_node(pd->type, "You cannot apply both `foreign` and `export` to a procedure"); + error_node(pd->type, "A foreign procedure cannot have an `export` tag"); } if (pd->body != NULL) { if (is_foreign) { - error_node(pd->body, "A procedure tagged as `foreign` cannot have a body"); + error_node(pd->body, "A foreign procedure cannot have a body"); + } + if (proc_type->Proc.c_vararg) { + error_node(pd->body, "A procedure with a `#c_vararg` field cannot have a body"); } d->scope = c->context.scope; @@ -360,7 +363,7 @@ void check_proc_decl(Checker *c, Entity *e, DeclInfo *d) { Type *other_type = base_type(f->type); if (!are_signatures_similar_enough(this_type, other_type)) { error_node(d->proc_decl, - "Redeclaration of #foreign procedure `%.*s` with different type signatures\n" + "Redeclaration of foreign procedure `%.*s` with different type signatures\n" "\tat %.*s(%td:%td)", LIT(name), LIT(pos.file), pos.line, pos.column); } diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 2f43197c8..eff8035c1 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -1049,6 +1049,7 @@ Type *check_get_params(Checker *c, Scope *scope, AstNode *_params, bool *is_vari } bool is_variadic = false; + bool is_c_vararg = false; Entity **variables = gb_alloc_array(c->allocator, Entity *, variable_count); isize variable_index = 0; for_array(i, params) { @@ -1114,10 +1115,19 @@ Type *check_get_params(Checker *c, Scope *scope, AstNode *_params, bool *is_vari if (p->flags&FieldFlag_no_alias) { if (!is_type_pointer(type)) { - error_node(params[i], "`no_alias` can only be applied to fields of pointer type"); + error_node(params[i], "`#no_alias` can only be applied to fields of pointer type"); p->flags &= ~FieldFlag_no_alias; // Remove the flag } } + if (p->flags&FieldFlag_c_vararg) { + if (p->type == NULL || + p->type->kind != AstNode_Ellipsis) { + error_node(params[i], "`#c_vararg` can only be applied to variadic type fields"); + p->flags &= ~FieldFlag_c_vararg; // Remove the flag + } else { + is_c_vararg = true; + } + } for_array(j, p->names) { AstNode *name = p->names[j]; @@ -1145,6 +1155,9 @@ Type *check_get_params(Checker *c, Scope *scope, AstNode *_params, bool *is_vari Entity *end = variables[variable_count-1]; end->type = make_type_slice(c->allocator, end->type); end->flags |= EntityFlag_Ellipsis; + if (is_c_vararg) { + end->flags |= EntityFlag_CVarArg; + } } Type *tuple = make_type_tuple(c->allocator); @@ -1409,6 +1422,19 @@ void check_procedure_type(Checker *c, Type *type, AstNode *proc_type_node) { type->Proc.variadic = variadic; type->Proc.calling_convention = pt->calling_convention; + if (param_count > 0) { + Entity *end = params->Tuple.variables[param_count-1]; + if (end->flags&EntityFlag_CVarArg) { + if (pt->calling_convention == ProcCC_Odin) { + error(end->token, "Odin calling convention does not support #c_vararg"); + } else if (pt->calling_convention == ProcCC_Fast) { + error(end->token, "Fast calling convention does not support #c_vararg"); + } else { + type->Proc.c_vararg = true; + } + } + } + type->Proc.abi_compat_params = gb_alloc_array(c->allocator, Type *, param_count); for (isize i = 0; i < param_count; i++) { @@ -4865,6 +4891,15 @@ CALL_ARGUMENT_CHECKER(check_call_arguments_internal) { if (score_) *score_ = score; return CallArgumentError_NonVariadicExpand; } + if (vari_expand && proc_type->Proc.c_vararg) { + if (show_error) { + error(ce->ellipsis, + "Cannot use `..` in call to a `#c_vararg` variadic procedure: `%.*s`", + LIT(ce->proc->Ident.string)); + } + if (score_) *score_ = score; + return CallArgumentError_NonVariadicExpand; + } if (operands.count == 0 && param_count_excluding_defaults == 0) { if (score_) *score_ = score; @@ -6566,6 +6601,9 @@ gbString write_expr_to_string(gbString str, AstNode *node) { if (f->flags&FieldFlag_no_alias) { str = gb_string_appendc(str, "#no_alias "); } + if (f->flags&FieldFlag_c_vararg) { + str = gb_string_appendc(str, "#c_vararg "); + } for_array(i, f->names) { AstNode *name = f->names[i]; diff --git a/src/entity.cpp b/src/entity.cpp index 6fa89d652..44464b49c 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -43,6 +43,9 @@ enum EntityFlag { EntityFlag_Value = 1<<9, EntityFlag_Sret = 1<<10, EntityFlag_BitFieldValue = 1<<11, + + EntityFlag_CVarArg = 1<<20, + }; // Zero value means the overloading process is not yet done diff --git a/src/ir.cpp b/src/ir.cpp index 2fafd7a8c..8fb3be9a7 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -1477,7 +1477,11 @@ irValue *ir_emit_call(irProcedure *p, irValue *value, irValue **args, isize arg_ Type *results = pt->Proc.results; isize param_count = pt->Proc.param_count; - GB_ASSERT(param_count == arg_count); + if (pt->Proc.c_vararg) { + GB_ASSERT(param_count-1 <= arg_count); + } else { + GB_ASSERT(param_count == arg_count); + } for (isize i = 0; i < param_count; i++) { Type *original_type = pt->Proc.params->Tuple.variables[i]->type; Type *new_type = pt->Proc.abi_compat_params[i]; @@ -4649,6 +4653,7 @@ irValue *ir_build_expr(irProcedure *proc, AstNode *expr) { irValue **args = gb_alloc_array(proc->module->allocator, irValue *, gb_max(type->param_count, arg_count)); bool variadic = type->variadic; bool vari_expand = ce->ellipsis.pos.line != 0; + bool is_c_vararg = type->c_vararg; for_array(i, ce->args) { irValue *a = ir_build_expr(proc, ce->args[i]); @@ -4683,7 +4688,26 @@ irValue *ir_build_expr(irProcedure *proc, AstNode *expr) { } - if (variadic) { + if (is_c_vararg) { + GB_ASSERT(variadic); + GB_ASSERT(!vari_expand); + isize i = 0; + for (; i < type->param_count-1; i++) { + args[i] = ir_emit_conv(proc, args[i], pt->variables[i]->type); + } + Type *variadic_type = pt->variables[i]->type; + GB_ASSERT(is_type_slice(variadic_type)); + variadic_type = base_type(variadic_type)->Slice.elem; + if (!is_type_any(variadic_type)) { + for (; i < arg_count; i++) { + args[i] = ir_emit_conv(proc, args[i], variadic_type); + } + } else { + for (; i < arg_count; i++) { + args[i] = ir_emit_conv(proc, args[i], default_type(ir_type(args[i]))); + } + } + } else if (variadic) { isize i = 0; for (; i < type->param_count-1; i++) { args[i] = ir_emit_conv(proc, args[i], pt->variables[i]->type); @@ -4702,7 +4726,12 @@ irValue *ir_build_expr(irProcedure *proc, AstNode *expr) { } } - if (variadic && !vari_expand) { + i64 final_count = type->param_count; + if (is_c_vararg) { + final_count = arg_count; + } + + if (variadic && !vari_expand && !is_c_vararg) { ir_emit_comment(proc, str_lit("variadic call argument generation")); gbAllocator allocator = proc->module->allocator; Type *slice_type = pt->variables[type->param_count-1]->type; @@ -4727,7 +4756,7 @@ irValue *ir_build_expr(irProcedure *proc, AstNode *expr) { args[arg_count-1] = ir_emit_load(proc, slice); } - return ir_emit_call(proc, value, args, type->param_count); + return ir_emit_call(proc, value, args, final_count); case_end; case_ast_node(se, SliceExpr, expr); diff --git a/src/ir_print.cpp b/src/ir_print.cpp index cc008f4a1..1328f9f6e 100644 --- a/src/ir_print.cpp +++ b/src/ir_print.cpp @@ -161,10 +161,11 @@ void ir_print_proc_results(irFileBuffer *f, irModule *m, Type *t) { isize count = rt->Tuple.variable_count; ir_fprintf(f, "{"); for (isize i = 0; i < count; i++) { + Entity *e = rt->Tuple.variables[i]; if (i > 0) { ir_fprintf(f, ", "); } - ir_print_type(f, m, rt->Tuple.variables[i]->type); + ir_print_type(f, m, e->type); } ir_fprintf(f, "}"); } @@ -172,6 +173,35 @@ void ir_print_proc_results(irFileBuffer *f, irModule *m, Type *t) { } +void ir_print_proc_type_without_pointer(irFileBuffer *f, irModule *m, Type *t) { + i64 word_bits = 8*build_context.word_size; + t = base_type(t); + GB_ASSERT(is_type_proc(t)); + + isize param_count = t->Proc.param_count; + isize result_count = t->Proc.result_count; + ir_print_proc_results(f, m, t); + ir_fprintf(f, " ("); + if (t->Proc.return_by_pointer) { + ir_print_type(f, m, reduce_tuple_to_single_type(t->Proc.results)); + ir_fprintf(f, "* sret noalias "); + if (param_count > 0) { + ir_fprintf(f, ", "); + } + } + for (isize i = 0; i < param_count; i++) { + if (i > 0) { + ir_fprintf(f, ", "); + } + if (i+1 == param_count && t->Proc.c_vararg) { + ir_fprintf(f, "..."); + } else { + ir_print_type(f, m, t->Proc.abi_compat_params[i]); + } + } + ir_fprintf(f, ")"); +} + void ir_print_type(irFileBuffer *f, irModule *m, Type *t) { i64 word_bits = 8*build_context.word_size; GB_ASSERT_NOT_NULL(t); @@ -328,24 +358,8 @@ void ir_print_type(irFileBuffer *f, irModule *m, Type *t) { } return; case Type_Proc: { - isize param_count = t->Proc.param_count; - isize result_count = t->Proc.result_count; - ir_print_proc_results(f, m, t); - ir_fprintf(f, " ("); - if (t->Proc.return_by_pointer) { - ir_print_type(f, m, reduce_tuple_to_single_type(t->Proc.results)); - ir_fprintf(f, "* sret noalias "); - if (param_count > 0) { - ir_fprintf(f, ", "); - } - } - for (isize i = 0; i < param_count; i++) { - if (i > 0) { - ir_fprintf(f, ", "); - } - ir_print_type(f, m, t->Proc.abi_compat_params[i]); - } - ir_fprintf(f, ")*"); + ir_print_proc_type_without_pointer(f, m, t); + ir_fprintf(f, "*"); } return; case Type_Map: { @@ -1216,13 +1230,16 @@ void ir_print_instr(irFileBuffer *f, irModule *m, irValue *value) { irInstrCall *call = &instr->Call; Type *proc_type = base_type(ir_type(call->value)); GB_ASSERT(is_type_proc(proc_type)); + bool is_c_vararg = proc_type->Proc.c_vararg; Type *result_type = call->type; if (result_type) { ir_fprintf(f, "%%%d = ", value->index); } ir_fprintf(f, "call "); ir_print_calling_convention(f, m, proc_type->Proc.calling_convention); - if (result_type && !proc_type->Proc.return_by_pointer) { + if (is_c_vararg) { + ir_print_proc_type_without_pointer(f, m, proc_type); + } else if (result_type && !proc_type->Proc.return_by_pointer) { ir_print_proc_results(f, m, proc_type); } else { ir_fprintf(f, "void"); @@ -1242,24 +1259,55 @@ void ir_print_instr(irFileBuffer *f, irModule *m, irValue *value) { } } + if (call->arg_count > 0) { Type *proc_type = base_type(ir_type(call->value)); GB_ASSERT(proc_type->kind == Type_Proc); TypeTuple *params = &proc_type->Proc.params->Tuple; - for (isize i = 0; i < call->arg_count; i++) { - Entity *e = params->variables[i]; - GB_ASSERT(e != NULL); - Type *t = proc_type->Proc.abi_compat_params[i]; - if (i > 0) { - ir_fprintf(f, ", "); + if (proc_type->Proc.c_vararg) { + isize i = 0; + for (; i < params->variable_count-1; i++) { + Entity *e = params->variables[i]; + GB_ASSERT(e != NULL); + Type *t = proc_type->Proc.abi_compat_params[i]; + if (i > 0) { + ir_fprintf(f, ", "); + } + ir_print_type(f, m, t); + if (e->flags&EntityFlag_NoAlias) { + ir_fprintf(f, " noalias"); + } + ir_fprintf(f, " "); + irValue *arg = call->args[i]; + ir_print_value(f, m, arg, t); } - ir_print_type(f, m, t); - if (e->flags&EntityFlag_NoAlias) { - ir_fprintf(f, " noalias"); + for (; i < call->arg_count; i++) { + if (i > 0) { + ir_fprintf(f, ", "); + } + + irValue *arg = call->args[i]; + Type *t = ir_type(arg); + ir_print_type(f, m, t); + ir_fprintf(f, " "); + ir_print_value(f, m, arg, t); + } + } else { + for (isize i = 0; i < call->arg_count; i++) { + Entity *e = params->variables[i]; + GB_ASSERT(e != NULL); + irValue *arg = call->args[i]; + Type *t = proc_type->Proc.abi_compat_params[i]; + if (i > 0) { + ir_fprintf(f, ", "); + } + ir_print_type(f, m, t); + if (e->flags&EntityFlag_NoAlias) { + ir_fprintf(f, " noalias"); + } + ir_fprintf(f, " "); + ir_print_value(f, m, arg, t); } - ir_fprintf(f, " "); - irValue *arg = call->args[i]; - ir_print_value(f, m, arg, t); } } ir_fprintf(f, ")\n"); @@ -1495,17 +1543,21 @@ void ir_print_proc(irFileBuffer *f, irModule *m, irProcedure *proc) { if (i > 0) { ir_fprintf(f, ", "); } - ir_print_type(f, m, abi_type); - if (e->flags&EntityFlag_NoAlias) { - ir_fprintf(f, " noalias"); - } - if (proc->body != NULL) { - if (e->token.string != "" && - e->token.string != "_") { - ir_fprintf(f, " "); - ir_print_encoded_local(f, e->token.string); - } else { - ir_fprintf(f, " %%_.param_%td", i); + if (i+1 == params->variable_count && proc_type->c_vararg) { + ir_fprintf(f, " ..."); + } else { + ir_print_type(f, m, abi_type); + if (e->flags&EntityFlag_NoAlias) { + ir_fprintf(f, " noalias"); + } + if (proc->body != NULL) { + if (e->token.string != "" && + e->token.string != "_") { + ir_fprintf(f, " "); + ir_print_encoded_local(f, e->token.string); + } else { + ir_fprintf(f, " %%_.param_%td", i); + } } } } diff --git a/src/parser.cpp b/src/parser.cpp index 8d7af3188..c22e0e19c 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -65,6 +65,7 @@ enum ProcTag { ProcTag_bounds_check = 1<<0, ProcTag_no_bounds_check = 1<<1, + ProcTag_require_results = 1<<4, ProcTag_foreign = 1<<10, @@ -72,6 +73,7 @@ enum ProcTag { ProcTag_link_name = 1<<12, ProcTag_inline = 1<<13, ProcTag_no_inline = 1<<14, + // ProcTag_dll_import = 1<<15, // ProcTag_dll_export = 1<<16, }; @@ -100,8 +102,9 @@ enum FieldFlag { FieldFlag_ellipsis = 1<<0, FieldFlag_using = 1<<1, FieldFlag_no_alias = 1<<2, + FieldFlag_c_vararg = 1<<3, - FieldFlag_Signature = FieldFlag_ellipsis|FieldFlag_using|FieldFlag_no_alias, + FieldFlag_Signature = FieldFlag_ellipsis|FieldFlag_using|FieldFlag_no_alias|FieldFlag_c_vararg, }; enum StmtAllowFlag { @@ -2999,7 +3002,7 @@ AstNode *parse_proc_type(AstFile *f, Token proc_token, String *link_name_) { parse_proc_tags(f, &tags, &link_name, &cc); - if (link_name_) *link_name_ = link_name; + if (link_name_) *link_name_ = link_name; return ast_proc_type(f, proc_token, params, results, tags, cc); } @@ -3030,6 +3033,7 @@ enum FieldPrefixKind { FieldPrefix_Using, FieldPrefix_NoAlias, + FieldPrefix_CVarArg, }; FieldPrefixKind is_token_field_prefix(AstFile *f) { @@ -3047,6 +3051,9 @@ FieldPrefixKind is_token_field_prefix(AstFile *f) { if (f->curr_token.string == "no_alias") { return FieldPrefix_NoAlias; } + if (f->curr_token.string == "c_vararg") { + return FieldPrefix_CVarArg; + } break; } } break; @@ -3058,6 +3065,7 @@ FieldPrefixKind is_token_field_prefix(AstFile *f) { u32 parse_field_prefixes(AstFile *f) { i32 using_count = 0; i32 no_alias_count = 0; + i32 c_vararg_count = 0; for (;;) { FieldPrefixKind kind = is_token_field_prefix(f); @@ -3067,15 +3075,18 @@ u32 parse_field_prefixes(AstFile *f) { switch (kind) { case FieldPrefix_Using: using_count += 1; next_token(f); break; case FieldPrefix_NoAlias: no_alias_count += 1; next_token(f); break; + case FieldPrefix_CVarArg: c_vararg_count += 1; next_token(f); break; } } if (using_count > 1) syntax_error(f->curr_token, "Multiple `using` in this field list"); if (no_alias_count > 1) syntax_error(f->curr_token, "Multiple `#no_alias` in this field list"); + if (c_vararg_count > 1) syntax_error(f->curr_token, "Multiple `#c_vararg` in this field list"); u32 field_flags = 0; if (using_count > 0) field_flags |= FieldFlag_using; if (no_alias_count > 0) field_flags |= FieldFlag_no_alias; + if (c_vararg_count > 0) field_flags |= FieldFlag_c_vararg; return field_flags; } @@ -3090,9 +3101,13 @@ u32 check_field_prefixes(AstFile *f, isize name_count, u32 allowed_flags, u32 se set_flags &= ~FieldFlag_using; } if ((allowed_flags&FieldFlag_no_alias) == 0 && (set_flags&FieldFlag_no_alias)) { - syntax_error(f->curr_token, "`no_alias` is not allowed within this field list"); + syntax_error(f->curr_token, "`#no_alias` is not allowed within this field list"); set_flags &= ~FieldFlag_no_alias; } + if ((allowed_flags&FieldFlag_c_vararg) == 0 && (set_flags&FieldFlag_c_vararg)) { + syntax_error(f->curr_token, "`#c_vararg` is not allowed within this field list"); + set_flags &= ~FieldFlag_c_vararg; + } return set_flags; } diff --git a/src/types.cpp b/src/types.cpp index a659d54f2..51c77d383 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -142,6 +142,7 @@ struct TypeRecord { Type * abi_compat_result_type; \ bool variadic; \ bool require_results; \ + bool c_vararg; \ ProcCallingConvention calling_convention; \ }) \ TYPE_KIND(Map, struct { \ @@ -1070,7 +1071,9 @@ bool are_types_identical(Type *x, Type *y) { if (y->kind == Type_Tuple) { if (x->Tuple.variable_count == y->Tuple.variable_count) { for (isize i = 0; i < x->Tuple.variable_count; i++) { - if (!are_types_identical(x->Tuple.variables[i]->type, y->Tuple.variables[i]->type)) { + Entity *xe = x->Tuple.variables[i]; + Entity *ye = y->Tuple.variables[i]; + if (!are_types_identical(xe->type, ye->type)) { return false; } } @@ -1082,6 +1085,7 @@ bool are_types_identical(Type *x, Type *y) { case Type_Proc: if (y->kind == Type_Proc) { return x->Proc.calling_convention == y->Proc.calling_convention && + x->Proc.c_vararg == y->Proc.c_vararg && x->Proc.variadic == y->Proc.variadic && are_types_identical(x->Proc.params, y->Proc.params) && are_types_identical(x->Proc.results, y->Proc.results); @@ -2292,6 +2296,9 @@ gbString write_type_to_string(gbString str, Type *type) { if (i > 0) { str = gb_string_appendc(str, ", "); } + if (var->flags&EntityFlag_CVarArg) { + str = gb_string_appendc(str, "#c_vararg "); + } if (var->flags&EntityFlag_Ellipsis) { Type *slice = base_type(var->type); str = gb_string_appendc(str, "..");