From: Brendan Hansen Date: Mon, 7 Sep 2020 21:47:57 +0000 (-0500) Subject: added initial implementation of typed varargs X-Git-Url: https://git.brendanfh.com/?a=commitdiff_plain;h=be8d8561d4f36cd58f9b309a7ba053f039436c0f;p=onyx.git added initial implementation of typed varargs --- diff --git a/.vimspector.json b/.vimspector.json index 2e8fdc8b..899b511c 100644 --- a/.vimspector.json +++ b/.vimspector.json @@ -6,7 +6,7 @@ "type": "cppdbg", "request": "launch", "program": "${workspaceFolder}/onyx", - "args": ["-verbose", "examples/02_variables.onyx"], + "args": ["-verbose", "progs/poly_test.onyx"], "stopAtEntry": true, "cwd": "${workspaceFolder}", "environment": [], diff --git a/core/stdio.onyx b/core/stdio.onyx index e2cfac11..2c74f7a3 100644 --- a/core/stdio.onyx +++ b/core/stdio.onyx @@ -12,8 +12,8 @@ stdio_init :: proc { } print_string :: proc (s: string) { - string_builder_append(^print_buffer, s); - if s.data[s.count - 1] == #char "\n" do print_buffer_flush(); + string_builder_append(^print_buffer, s); + if s.data[s.count - 1] == #char "\n" do print_buffer_flush(); } print_cstring :: proc (s: cstring) do string_builder_append(^print_buffer, s); diff --git a/docs/plan b/docs/plan index 19b7f2e5..604acdcf 100644 --- a/docs/plan +++ b/docs/plan @@ -245,13 +245,7 @@ HOW: - Do multiple passes if needed - Some APIs like WebGL have a ton of foreigns, and most of them aren't even used - [ ] baked parameters - - Compile time known parameters - - Removes the argument from the list and replaces the function with the - baked function - - [ ] Add SIMD intrinsics - - This also requires adding the v128 SIMD type + [X] Variadic arguments [ ] Put type info in data section so it is runtime accessible - type name @@ -260,6 +254,14 @@ HOW: - struct member names - array length + [ ] baked parameters + - Compile time known parameters + - Removes the argument from the list and replaces the function with the + baked function + + [ ] Add SIMD intrinsics + - This also requires adding the v128 SIMD type + [ ] Type parameterized structs [ ] Array literals @@ -284,8 +286,6 @@ HOW: - But, profiling says we are spending 50% of the program execution time in the lexer - That is awful - [ ] Variadic arguments - [ ] 'when' statements - Compile time conditions - Only evalutate code blocks that evaluate to be true diff --git a/include/onyxastnodes.h b/include/onyxastnodes.h index f32097e8..66da4b2b 100644 --- a/include/onyxastnodes.h +++ b/include/onyxastnodes.h @@ -179,6 +179,8 @@ typedef enum AstFlags { Ast_Flag_No_Clone = BH_BIT(18), Ast_Flag_Cannot_Take_Addr = BH_BIT(19), + + Ast_Flag_Arg_Is_VarArg = BH_BIT(20), } AstFlags; typedef enum UnaryOp { @@ -468,8 +470,13 @@ struct AstGlobal { }; }; }; -struct AstParam { AstLocal *local; AstTyped *default_value; }; -struct AstFunction { +struct AstParam { + AstLocal *local; + AstTyped *default_value; + + b32 is_vararg : 1; +}; +struct AstFunction { AstTyped_base; Scope *scope; diff --git a/include/onyxtypes.h b/include/onyxtypes.h index 6eb3686b..5dfe489a 100644 --- a/include/onyxtypes.h +++ b/include/onyxtypes.h @@ -70,7 +70,8 @@ typedef struct StructMember { TYPE_KIND(Array, struct { u32 size; u32 count; Type *elem; }) \ TYPE_KIND(Slice, struct { Type *ptr_to_data; }) \ TYPE_KIND(DynArray, struct { Type *ptr_to_data; }) \ - TYPE_KIND(Enum, struct { char* name; Type* backing; }) + TYPE_KIND(VarArgs, struct { Type *ptr_to_data; }) \ + TYPE_KIND(Enum, struct { char* name; Type* backing; }) typedef enum TypeKind { Type_Kind_Invalid, @@ -117,6 +118,7 @@ Type* type_build_function_type(bh_allocator alloc, struct AstFunction* func); Type* type_make_pointer(bh_allocator alloc, Type* to); Type* type_make_slice(bh_allocator alloc, Type* of); Type* type_make_dynarray(bh_allocator alloc, Type* of); +Type* type_make_varargs(bh_allocator alloc, Type* of); const char* type_get_name(Type* type); u32 type_get_alignment_log2(Type* type); diff --git a/onyx b/onyx index a9c8ea99..ada98626 100755 Binary files a/onyx and b/onyx differ diff --git a/progs/odin_example.onyx b/progs/odin_example.onyx new file mode 100644 index 00000000..24c29d1f --- /dev/null +++ b/progs/odin_example.onyx @@ -0,0 +1,25 @@ +#include_file "core/std/wasi" + +use package core + +main :: proc (args: [] cstring) { + program := "+ + * s - /"; + accumulator := 0; + + for token: program { + switch token { + case #char "+" do accumulator += 1; + case #char "-" do accumulator -= 1; + case #char "*" do accumulator *= 2; + case #char "/" do accumulator /= 2; + case #char "s" do accumulator *= accumulator; + case #default --- + } + } + + print("The program \""); + print(program); + print("\" calculates the value "); + print(accumulator); + print("\n"); +} diff --git a/progs/poly_test.onyx b/progs/poly_test.onyx index cf256a0a..212d7a94 100644 --- a/progs/poly_test.onyx +++ b/progs/poly_test.onyx @@ -100,9 +100,62 @@ switch_demo :: proc { } } +vararg_test :: proc (prefix: string, nums: ..i32) { + print(prefix); + for num: nums { + print(num); + print(" "); + } +} + +NumInfo :: struct { + min : i32; + max : i32; + sum : i32; +} + +get_num_info :: proc (nums: ..i32) -> NumInfo { + ni : NumInfo; + + ni.min = nums[0]; + for num: nums do if num < ni.min do ni.min = num; + + ni.max = nums[0]; + for num: nums do if num > ni.max do ni.max = num; + + ni.sum = 0; + for num: nums do ni.sum += num; + + return ni; +} + +print_strings :: proc (ss: ..string) { + for s: ss do print_string(s); +} + +// At some point, it would be nice to have the ability to say something like: +// complex_varargs :: proc (nums: ..i32, names: ..string) { +// ... +// } + main :: proc (args: [] cstring) { switch_demo(); + print_strings("This ", "is ", "a ", "test.\n"); + + vararg_test("Here are some numbers:\n", 1, 2, 3, 4, 5); + print("\n\n"); + + ni := get_num_info(1, 2, 3, 4, 5); + print("Some information about those numbers:\n"); + print("Min: "); + print(ni.min); + print("\nMax: "); + print(ni.max); + print("\nSum: "); + print(ni.sum); + print("\n\n"); + res := compose(5, proc (x: i32) -> i32 do return x * 3;, proc (x: i32) -> i32 do return x + 5;); print(res); diff --git a/src/onyxchecker.c b/src/onyxchecker.c index 5b697a9e..7815d038 100644 --- a/src/onyxchecker.c +++ b/src/onyxchecker.c @@ -134,6 +134,19 @@ b32 check_for(AstFor* fornode) { fornode->loop_type = For_Loop_Slice; } + else if (iter_type->kind == Type_Kind_VarArgs) { + if (fornode->by_pointer) { + onyx_report_error(fornode->var->token->pos, "Cannot iterate by pointer over '%s'.", type_get_name(iter_type)); + return 1; + } + + can_iterate = 1; + + fornode->var->type = iter_type->VarArgs.ptr_to_data->Pointer.elem; + + // NOTE: Slices are VarArgs are being treated the same here. + fornode->loop_type = For_Loop_Slice; + } else if (iter_type->kind == Type_Kind_DynArray) { can_iterate = 1; @@ -219,7 +232,11 @@ static AstTyped* match_overloaded_function(AstCall* call, AstOverloadedFunction* while (arg != NULL) { fill_in_type((AstTyped *) arg); - if (!types_are_compatible(*param_type, arg->type)) goto no_match; + if ((*param_type)->kind == Type_Kind_VarArgs) { + if (!types_are_compatible((*param_type)->VarArgs.ptr_to_data->Pointer.elem, arg->type)) + goto no_match; + } + else if (!types_are_compatible(*param_type, arg->type)) goto no_match; param_type++; arg = (AstArgument *) arg->next; @@ -376,7 +393,7 @@ b32 check_call(AstCall* call) { } } - // NOTE: If we calling an intrinsic function, translate the + // NOTE: If we are calling an intrinsic function, translate the // call into an intrinsic call node. if (callee->flags & Ast_Flag_Intrinsic) { call->kind = Ast_Kind_Intrinsic_Call; @@ -444,14 +461,38 @@ b32 check_call(AstCall* call) { Type **formal_params = callee->type->Function.params; actual = call->arguments; + Type* variadic_type = NULL; + i32 arg_pos = 0; - while (arg_pos < callee->type->Function.param_count && actual != NULL) { - if (!types_are_compatible(formal_params[arg_pos], actual->type)) { + while (1) { + if (actual == NULL) break; + if (arg_pos >= callee->type->Function.param_count && variadic_type == NULL) break; + + if (variadic_type == NULL && formal_params[arg_pos]->kind == Type_Kind_VarArgs) { + variadic_type = formal_params[arg_pos]->VarArgs.ptr_to_data->Pointer.elem; + } + + if (variadic_type != NULL) { + if (!types_are_compatible(variadic_type, actual->type)) { + onyx_report_error(actual->token->pos, + "The function '%b' expects a value of type '%s' for the variadic parameter, '%b', got '%s'.", + callee->token->text, callee->token->length, + type_get_name(formal_params[arg_pos]), + callee->params[arg_pos].local->token->text, + callee->params[arg_pos].local->token->length, + type_get_name(actual->type)); + return 1; + } + + actual->flags |= Ast_Flag_Arg_Is_VarArg; + + } else if (!types_are_compatible(formal_params[arg_pos], actual->type)) { onyx_report_error(actual->token->pos, - "The function '%b' expects a value of type '%s' for parameter '%d', got '%s'.", + "The function '%b' expects a value of type '%s' for %d%s parameter, got '%s'.", callee->token->text, callee->token->length, type_get_name(formal_params[arg_pos]), - arg_pos, + arg_pos + 1, + bh_num_suffix(arg_pos + 1), type_get_name(actual->type)); return 1; } @@ -884,7 +925,8 @@ b32 check_array_access(AstArrayAccess* aa) { else if (aa->addr->type->kind == Type_Kind_Array) aa->type = aa->addr->type->Array.elem; else if (aa->addr->type->kind == Type_Kind_Slice - || aa->addr->type->kind == Type_Kind_DynArray) { + || aa->addr->type->kind == Type_Kind_DynArray + || aa->addr->type->kind == Type_Kind_VarArgs) { // If we are accessing on a slice or a dynamic array, implicitly add a field access for the data member StructMember smem; @@ -1223,6 +1265,7 @@ b32 check_struct(AstStructType* s_node) { b32 check_function_header(AstFunction* func) { b32 expect_default_param = 0; + b32 has_had_varargs = 0; bh_arr_each(AstParam, param, func->params) { AstLocal* local = param->local; @@ -1233,6 +1276,18 @@ b32 check_function_header(AstFunction* func) { return 1; } + if (has_had_varargs && param->is_vararg) { + onyx_report_error(local->token->pos, + "Can only have one param that is of variable argument type."); + return 1; + } + + if (has_had_varargs && !param->is_vararg) { + onyx_report_error(local->token->pos, + "Variable arguments must be last in parameter list"); + return 1; + } + if (param->default_value != NULL) expect_default_param = 1; fill_in_type((AstTyped *) local); @@ -1242,6 +1297,12 @@ b32 check_function_header(AstFunction* func) { return 1; } + if (param->is_vararg) { + has_had_varargs = 1; + + local->type = type_make_varargs(semstate.node_allocator, local->type); + } + if (local->type->kind != Type_Kind_Array && type_size_of(local->type) == 0) { onyx_report_error(local->token->pos, "Function parameters cannot have zero-width types."); diff --git a/src/onyxparser.c b/src/onyxparser.c index 9fa2a706..f31bb666 100644 --- a/src/onyxparser.c +++ b/src/onyxparser.c @@ -1419,6 +1419,11 @@ static void parse_function_params(OnyxParser* parser, AstFunction* func) { } if (parser->curr->type != '=') { + if (parser->curr->type == Token_Type_Dot_Dot) { + consume_token(parser); + curr_param.is_vararg = 1; + } + i32 old_len = bh_arr_length(*parser->polymorph_context.poly_params); curr_param.local->type_node = parse_type(parser); diff --git a/src/onyxtypes.c b/src/onyxtypes.c index 5fcbe809..0132c26f 100644 --- a/src/onyxtypes.c +++ b/src/onyxtypes.c @@ -94,6 +94,11 @@ b32 types_are_surface_compatible(Type* t1, Type* t2) { return types_are_compatible(t1->Slice.ptr_to_data->Pointer.elem, t2->Slice.ptr_to_data->Pointer.elem); } + case Type_Kind_VarArgs: { + if (t2->kind != Type_Kind_VarArgs) return 0; + return types_are_compatible(t1->VarArgs.ptr_to_data->Pointer.elem, t2->VarArgs.ptr_to_data->Pointer.elem); + } + case Type_Kind_DynArray: { if (t2->kind != Type_Kind_DynArray) return 0; return types_are_compatible(t1->DynArray.ptr_to_data->Pointer.elem, t2->DynArray.ptr_to_data->Pointer.elem); @@ -191,6 +196,11 @@ b32 types_are_compatible(Type* t1, Type* t2) { return types_are_compatible(t1->Slice.ptr_to_data->Pointer.elem, t2->Slice.ptr_to_data->Pointer.elem); } + case Type_Kind_VarArgs: { + if (t2->kind != Type_Kind_VarArgs) return 0; + return types_are_compatible(t1->VarArgs.ptr_to_data->Pointer.elem, t2->VarArgs.ptr_to_data->Pointer.elem); + } + case Type_Kind_DynArray: { if (t2->kind != Type_Kind_DynArray) return 0; return types_are_compatible(t1->DynArray.ptr_to_data->Pointer.elem, t2->DynArray.ptr_to_data->Pointer.elem); @@ -215,6 +225,7 @@ u32 type_size_of(Type* type) { case Type_Kind_Struct: return type->Struct.size; case Type_Kind_Enum: return type_size_of(type->Enum.backing); case Type_Kind_Slice: return 8; + case Type_Kind_VarArgs: return 8; case Type_Kind_DynArray: return 12; default: return 0; } @@ -231,6 +242,7 @@ u32 type_alignment_of(Type* type) { case Type_Kind_Struct: return type->Struct.alignment; case Type_Kind_Enum: return type_alignment_of(type->Enum.backing); case Type_Kind_Slice: return 4; + case Type_Kind_VarArgs: return 4; case Type_Kind_DynArray: return 4; default: return 1; } @@ -410,7 +422,7 @@ Type* type_build_function_type(bh_allocator alloc, AstFunction* func) { if (param_count > 0) { i32 i = 0; bh_arr_each(AstParam, param, func->params) { - if (param->default_value == NULL) + if (param->default_value == NULL && !param->is_vararg) func_type->Function.needed_param_count++; func_type->Function.params[i++] = param->local->type; } @@ -446,6 +458,14 @@ Type* type_make_dynarray(bh_allocator alloc, Type* of) { return dynarr; } +Type* type_make_varargs(bh_allocator alloc, Type* of) { + Type* va_type = bh_alloc(alloc, sizeof(Type)); + va_type->kind = Type_Kind_VarArgs; + va_type->VarArgs.ptr_to_data = type_make_pointer(alloc, of); + + return va_type; +} + const char* type_get_name(Type* type) { if (type == NULL) return "unknown"; @@ -465,6 +485,7 @@ const char* type_get_name(Type* type) { return ""; case Type_Kind_Slice: return bh_aprintf(global_scratch_allocator, "[] %s", type_get_name(type->Slice.ptr_to_data->Pointer.elem)); + case Type_Kind_VarArgs: return bh_aprintf(global_scratch_allocator, "..%s", type_get_name(type->VarArgs.ptr_to_data->Pointer.elem)); case Type_Kind_DynArray: return bh_aprintf(global_scratch_allocator, "[..] %s", type_get_name(type->DynArray.ptr_to_data->Pointer.elem)); case Type_Kind_Function: { @@ -509,6 +530,7 @@ b32 type_lookup_member(Type* type, char* member, StructMember* smem) { return 1; } + case Type_Kind_VarArgs: case Type_Kind_Slice: { if (strcmp(member, "data") == 0) { smem->idx = 0; @@ -563,6 +585,9 @@ b32 type_lookup_member_by_idx(Type* type, i32 idx, StructMember* smem) { return 1; } + // HACK: This relies on the fact that the structures for Slice and VarArgs + // are identical. - brendanfh 2020/09/07 + case Type_Kind_VarArgs: case Type_Kind_Slice: { if (idx == 0) { smem->idx = 0; @@ -681,6 +706,7 @@ b32 type_is_array_accessible(Type* type) { if (type_is_pointer(type)) return 1; if (type->kind == Type_Kind_Slice) return 1; if (type->kind == Type_Kind_DynArray) return 1; + if (type->kind == Type_Kind_VarArgs) return 1; return 0; } @@ -688,6 +714,7 @@ b32 type_is_structlike(Type* type) { if (type->kind == Type_Kind_Struct) return 1; if (type->kind == Type_Kind_Slice) return 1; if (type->kind == Type_Kind_DynArray) return 1; + if (type->kind == Type_Kind_VarArgs) return 1; if (type->kind == Type_Kind_Pointer) { if (type->Pointer.elem->kind == Type_Kind_Struct) return 1; if (type->Pointer.elem->kind == Type_Kind_Slice) return 1; @@ -700,6 +727,7 @@ b32 type_is_structlike_strict(Type* type) { if (type->kind == Type_Kind_Struct) return 1; if (type->kind == Type_Kind_Slice) return 1; if (type->kind == Type_Kind_DynArray) return 1; + if (type->kind == Type_Kind_VarArgs) return 1; return 0; } @@ -707,6 +735,7 @@ u32 type_structlike_mem_count(Type* type) { switch (type->kind) { case Type_Kind_Struct: return type->Struct.mem_count; case Type_Kind_Slice: return 2; + case Type_Kind_VarArgs: return 2; case Type_Kind_DynArray: return 3; default: return 0; } @@ -716,7 +745,8 @@ u32 type_structlike_is_simple(Type* type) { switch (type->kind) { case Type_Kind_Struct: return type_struct_is_simple(type); case Type_Kind_Slice: return 1; + case Type_Kind_VarArgs: return 1; case Type_Kind_DynArray: return 1; default: return 0; } -} \ No newline at end of file +} diff --git a/src/onyxwasm.c b/src/onyxwasm.c index f47dd4aa..d98c8a47 100644 --- a/src/onyxwasm.c +++ b/src/onyxwasm.c @@ -883,7 +883,7 @@ EMIT_FUNC(for_array, AstFor* for_node, u64 iter_local) { // NOTE: Storing structs requires that the location to store it is, // the top most thing on the stack. Everything requires it to be // 'under' the other element being stored. -brendanfh 2020/09/04 - if (!it_is_local && var->type->kind != Type_Kind_Struct) { + if (!it_is_local && !type_is_structlike(var->type)) { emit_local_location(mod, &code, var, &offset); } @@ -892,7 +892,7 @@ EMIT_FUNC(for_array, AstFor* for_node, u64 iter_local) { if (it_is_local) { WIL(WI_LOCAL_SET, iter_local); } else { - if (var->type->kind != Type_Kind_Struct) { + if (!type_is_structlike(var->type)) { emit_store_instruction(mod, &code, var->type, offset); } else { emit_local_location(mod, &code, var, &offset); @@ -979,7 +979,7 @@ EMIT_FUNC(for_slice, AstFor* for_node, u64 iter_local) { // NOTE: Storing structs requires that the location to store it is, // the top most thing on the stack. Everything requires it to be // 'under' the other element being stored. -brendanfh 2020/09/04 - if (!it_is_local && var->type->kind != Type_Kind_Struct) { + if (!it_is_local && !type_is_structlike(var->type)) { emit_local_location(mod, &code, var, &offset); } @@ -988,7 +988,7 @@ EMIT_FUNC(for_slice, AstFor* for_node, u64 iter_local) { if (it_is_local) { WIL(WI_LOCAL_SET, iter_local); } else { - if (var->type->kind != Type_Kind_Struct) { + if (!type_is_structlike(var->type)) { emit_store_instruction(mod, &code, var->type, offset); } else { emit_local_location(mod, &code, var, &offset); @@ -1279,10 +1279,33 @@ EMIT_FUNC(unaryop, AstUnaryOp* unop) { EMIT_FUNC(call, AstCall* call) { bh_arr(WasmInstruction) code = *pcode; + u32 stack_grow_amm = 0; + u64 stack_top_idx = bh_imap_get(&mod->index_map, (u64) &builtin_stack_top); + + u32 vararg_count = 0; + for (AstArgument *arg = call->arguments; arg != NULL; arg = (AstArgument *) arg->next) { + if ((arg->flags & Ast_Flag_Arg_Is_VarArg) && !type_is_structlike(arg->type)) { + WID(WI_GLOBAL_GET, stack_top_idx); + } + emit_expression(mod, &code, arg->value); + + if (arg->flags & Ast_Flag_Arg_Is_VarArg) { + if (type_is_structlike(arg->type)) WID(WI_GLOBAL_GET, stack_top_idx); + + emit_store_instruction(mod, &code, arg->type, stack_grow_amm); + + stack_grow_amm += type_size_of(arg->type); + vararg_count += 1; + } + } + + if (vararg_count > 0) { + WID(WI_GLOBAL_GET, stack_top_idx); + WID(WI_I32_CONST, vararg_count); } CallingConvention cc = type_function_get_cc(call->callee->type); @@ -1293,11 +1316,13 @@ EMIT_FUNC(call, AstCall* call) { u32 return_align = type_alignment_of(return_type); bh_align(return_size, return_align); - u64 stack_top_idx = bh_imap_get(&mod->index_map, (u64) &builtin_stack_top); + stack_grow_amm += return_size; - if (cc == CC_Return_Stack) { + b32 needs_stack = (cc == CC_Return_Stack) || (vararg_count > 0); + + if (needs_stack) { WID(WI_GLOBAL_GET, stack_top_idx); - WID(WI_I32_CONST, return_size); + WID(WI_I32_CONST, stack_grow_amm); WI(WI_I32_ADD); WID(WI_GLOBAL_SET, stack_top_idx); } @@ -1313,14 +1338,16 @@ EMIT_FUNC(call, AstCall* call) { WID(WI_CALL_INDIRECT, ((WasmInstructionData) { type_idx, 0x00 })); } - if (cc == CC_Return_Stack) { + if (needs_stack) { WID(WI_GLOBAL_GET, stack_top_idx); - WID(WI_I32_CONST, return_size); + WID(WI_I32_CONST, stack_grow_amm); WI(WI_I32_SUB); WID(WI_GLOBAL_SET, stack_top_idx); + } + if (cc == CC_Return_Stack) { WID(WI_GLOBAL_GET, stack_top_idx); - emit_load_instruction(mod, &code, return_type, 0); + emit_load_instruction(mod, &code, return_type, stack_grow_amm - return_size); } *pcode = code; @@ -2359,6 +2386,8 @@ static void emit_string_literal(OnyxWasmModule* mod, AstStrLit* strlit) { case 'r': *des++ = '\r'; break; case 'v': *des++ = '\v'; break; case 'e': *des++ = '\e'; break; + case '"': *des++ = '"'; break; + case '\\': *des++ = '\\'; break; case 'x': { // HACK: This whole way of doing this i++;