diff --git a/src/tilde.cpp b/src/tilde.cpp index 0cbc975c4..5562d2d48 100644 --- a/src/tilde.cpp +++ b/src/tilde.cpp @@ -189,6 +189,19 @@ gb_internal cgAddr cg_addr(cgValue const &value) { return addr; } +gb_internal cgAddr cg_addr_map(cgValue addr, cgValue map_key, Type *map_type, Type *map_result) { + GB_ASSERT(is_type_pointer(addr.type)); + Type *mt = type_deref(addr.type); + GB_ASSERT(is_type_map(mt)); + + cgAddr v = {cgAddr_Map, addr}; + v.map.key = map_key; + v.map.type = map_type; + v.map.result = map_result; + return v; +} + + gb_internal void cg_set_debug_pos_from_node(cgProcedure *p, Ast *node) { if (node) { TokenPos pos = ast_token(node).pos; @@ -435,6 +448,8 @@ gb_internal cgModule *cg_module_create(Checker *c) { map_init(&m->hasher_procs); map_init(&m->map_get_procs); map_init(&m->map_set_procs); + map_init(&m->map_info_map); + map_init(&m->map_cell_info_map); array_init(&m->single_threaded_procedure_queue, heap_allocator()); @@ -461,6 +476,8 @@ gb_internal void cg_module_destroy(cgModule *m) { map_destroy(&m->hasher_procs); map_destroy(&m->map_get_procs); map_destroy(&m->map_set_procs); + map_destroy(&m->map_info_map); + map_destroy(&m->map_cell_info_map); array_free(&m->single_threaded_procedure_queue); @@ -784,7 +801,6 @@ gb_internal bool cg_generate_code(Checker *c, LinkerData *linker_data) { } - TB_DebugFormat debug_format = TB_DEBUGFMT_NONE; if (build_context.ODIN_DEBUG) { switch (build_context.metrics.os) { diff --git a/src/tilde.hpp b/src/tilde.hpp index 5944c9ef7..4bc221e29 100644 --- a/src/tilde.hpp +++ b/src/tilde.hpp @@ -237,6 +237,9 @@ struct cgModule { PtrMap map_get_procs; PtrMap map_set_procs; + RecursiveMutex map_info_mutex; + PtrMap map_info_map; + PtrMap map_cell_info_map; // NOTE(bill): no need to protect this with a mutex PtrMap file_id_map; // Key: AstFile.id (i32 cast to uintptr) @@ -289,6 +292,7 @@ gb_internal cgValue cg_value(TB_Symbol * s, Type *type); gb_internal cgValue cg_value(TB_Node * node, Type *type); gb_internal cgAddr cg_addr(cgValue const &value); +gb_internal cgAddr cg_addr_map(cgValue addr, cgValue map_key, Type *map_type, Type *map_result); gb_internal u64 cg_typeid_as_u64(cgModule *m, Type *type); gb_internal cgValue cg_type_info(cgProcedure *p, Type *type); @@ -370,4 +374,7 @@ gb_internal cgValue cg_handle_param_value(cgProcedure *p, Type *parameter_type, gb_internal cgValue cg_builtin_len(cgProcedure *p, cgValue value); gb_internal cgValue cg_builtin_raw_data(cgProcedure *p, cgValue const &x); - +gb_internal cgValue cg_builtin_map_info(cgProcedure *p, Type *map_type); +gb_internal cgValue cg_builtin_map_cell_info(cgProcedure *p, Type *type); +gb_internal cgValue cg_emit_source_code_location_as_global(cgProcedure *p, String const &proc_name, TokenPos pos); +gb_internal cgValue cg_emit_source_code_location_as_global(cgProcedure *p, Ast *node); diff --git a/src/tilde_builtin.cpp b/src/tilde_builtin.cpp index f036ce583..d84506e09 100644 --- a/src/tilde_builtin.cpp +++ b/src/tilde_builtin.cpp @@ -231,6 +231,76 @@ gb_internal cgValue cg_builtin_mem_copy_non_overlapping(cgProcedure *p, cgValue return dst; } +gb_internal TB_Symbol *cg_builtin_map_cell_info_symbol(cgModule *m, Type *type) { + MUTEX_GUARD(&m->map_info_mutex); + TB_Symbol **found = map_get(&m->map_cell_info_map, type); + if (found) { + return *found; + } + i64 size = 0, len = 0; + map_cell_size_and_len(type, &size, &len); + + TB_Global *global = tb_global_create(m->mod, 0, "", cg_debug_type(m, t_map_cell_info), TB_LINKAGE_PRIVATE); + tb_global_set_storage(m->mod, tb_module_get_rdata(m->mod), global, type_size_of(t_map_cell_info), type_align_of(t_map_cell_info), 4); + + i64 ptr_size = build_context.ptr_size; + void *size_of_type = tb_global_add_region(m->mod, global, 0*ptr_size, ptr_size); + void *align_of_type = tb_global_add_region(m->mod, global, 1*ptr_size, ptr_size); + void *size_of_cell = tb_global_add_region(m->mod, global, 2*ptr_size, ptr_size); + void *elements_per_cell = tb_global_add_region(m->mod, global, 3*ptr_size, ptr_size); + + cg_write_uint_at_ptr(size_of_type, type_size_of(type), t_uintptr); + cg_write_uint_at_ptr(align_of_type, type_align_of(type), t_uintptr); + cg_write_uint_at_ptr(size_of_cell, size, t_uintptr); + cg_write_uint_at_ptr(elements_per_cell, len, t_uintptr); + + map_set(&m->map_cell_info_map, type, cast(TB_Symbol *)global); + + return cast(TB_Symbol *)global; +} + + +gb_internal cgValue cg_builtin_map_cell_info(cgProcedure *p, Type *type) { + type = core_type(type); + TB_Symbol *symbol = cg_builtin_map_cell_info_symbol(p->module, type); + TB_Node *node = tb_inst_get_symbol_address(p->func, symbol); + return cg_value(node, t_map_cell_info_ptr); +} + +gb_internal cgValue cg_builtin_map_info(cgProcedure *p, Type *map_type) { + map_type = base_type(map_type); + GB_ASSERT(map_type->kind == Type_Map); + + cgModule *m = p->module; + MUTEX_GUARD(&m->map_info_mutex); + TB_Global *global = nullptr; + TB_Symbol **found = map_get(&m->map_info_map, map_type); + if (found) { + global = cast(TB_Global *)*found; + } else { + global = tb_global_create(m->mod, 0, "", cg_debug_type(m, t_map_info), TB_LINKAGE_PRIVATE); + tb_global_set_storage(m->mod, tb_module_get_rdata(m->mod), global, type_size_of(t_map_info), type_align_of(t_map_info), 4); + + TB_Symbol *key_cell_info = cg_builtin_map_cell_info_symbol(m, map_type->Map.key); + TB_Symbol *value_cell_info = cg_builtin_map_cell_info_symbol(m, map_type->Map.value); + cgProcedure *key_hasher = cg_hasher_proc_for_type(p->module, map_type->Map.key); + cgProcedure *key_equal = cg_equal_proc_for_type (p->module, map_type->Map.key); + + tb_global_add_symbol_reloc(p->module->mod, global, 0*build_context.ptr_size, key_cell_info); + tb_global_add_symbol_reloc(p->module->mod, global, 1*build_context.ptr_size, value_cell_info); + tb_global_add_symbol_reloc(p->module->mod, global, 2*build_context.ptr_size, key_hasher->symbol); + tb_global_add_symbol_reloc(p->module->mod, global, 3*build_context.ptr_size, key_equal->symbol); + + map_set(&m->map_info_map, map_type, cast(TB_Symbol *)global); + } + + GB_ASSERT(global != nullptr); + TB_Node *node = tb_inst_get_symbol_address(p->func, cast(TB_Symbol *)global); + return cg_value(node, t_map_info_ptr); +} + + + gb_internal cgValue cg_build_builtin(cgProcedure *p, BuiltinProcId id, Ast *expr) { ast_node(ce, CallExpr, expr); @@ -434,6 +504,65 @@ gb_internal cgValue cg_build_builtin(cgProcedure *p, BuiltinProcId id, Ast *expr case BuiltinProc_type_hasher_proc: return cg_hasher_proc_value_for_type(p, ce->args[0]->tav.type); + + case BuiltinProc_type_map_cell_info: + return cg_builtin_map_cell_info(p, ce->args[0]->tav.type); + case BuiltinProc_type_map_info: + return cg_builtin_map_info(p, ce->args[0]->tav.type); + + case BuiltinProc_expect: + { + Type *t = default_type(expr->tav.type); + cgValue x = cg_emit_conv(p, cg_build_expr(p, ce->args[0]), t); + cgValue y = cg_emit_conv(p, cg_build_expr(p, ce->args[1]), t); + gb_unused(y); + return x; + } + + case BuiltinProc_count_leading_zeros: + { + cgValue n = cg_build_expr(p, ce->args[0]); + n = cg_emit_conv(p, n, default_type(expr->tav.type)); + GB_ASSERT(n.kind == cgValue_Value); + TB_Node *val = tb_inst_clz(p->func, n.node); + val = tb_inst_zxt(p->func, val, cg_data_type(n.type)); + return cg_value(val, n.type); + } + + + case BuiltinProc_count_trailing_zeros: + { + cgValue n = cg_build_expr(p, ce->args[0]); + n = cg_emit_conv(p, n, default_type(expr->tav.type)); + GB_ASSERT(n.kind == cgValue_Value); + TB_Node *val = tb_inst_ctz(p->func, n.node); + val = tb_inst_zxt(p->func, val, cg_data_type(n.type)); + return cg_value(val, n.type); + } + + case BuiltinProc_count_ones: + { + cgValue n = cg_build_expr(p, ce->args[0]); + n = cg_emit_conv(p, n, default_type(expr->tav.type)); + GB_ASSERT(n.kind == cgValue_Value); + TB_Node *val = tb_inst_popcount(p->func, n.node); + val = tb_inst_zxt(p->func, val, cg_data_type(n.type)); + return cg_value(val, n.type); + } + + case BuiltinProc_count_zeros: + { + cgValue n = cg_build_expr(p, ce->args[0]); + n = cg_emit_conv(p, n, default_type(expr->tav.type)); + GB_ASSERT(n.kind == cgValue_Value); + TB_DataType dt = cg_data_type(n.type); + TB_Node *ones = tb_inst_popcount(p->func, n.node); + ones = tb_inst_zxt(p->func, ones, dt); + + cgValue size = cg_const_int(p, n.type, 8*type_size_of(n.type)); + return cg_emit_arith(p, Token_Sub, size, cg_value(ones, n.type), n.type); + } + } diff --git a/src/tilde_const.cpp b/src/tilde_const.cpp index f9187e3e1..691409fe9 100644 --- a/src/tilde_const.cpp +++ b/src/tilde_const.cpp @@ -96,7 +96,11 @@ gb_internal cgValue cg_emit_source_code_location_as_global(cgProcedure *p, Strin return cg_lvalue_addr(ptr, t_source_code_location); } - +gb_internal cgValue cg_emit_source_code_location_as_global(cgProcedure *p, Ast *node) { + String proc_name = p->name; + TokenPos pos = ast_token(node).pos; + return cg_emit_source_code_location_as_global(p, proc_name, pos); +} gb_internal void cg_write_big_int_at_ptr(void *dst, BigInt const *a, Type *original_type) { GB_ASSERT(build_context.endian_kind == TargetEndian_Little); @@ -949,7 +953,12 @@ gb_internal cgValue cg_const_value(cgProcedure *p, Type *type, ExactValue const GB_ASSERT(!TB_IS_VOID_TYPE(dt)); // GB_ASSERT(dt.raw != TB_TYPE_I128.raw); if (is_type_unsigned(type)) { - u64 i = exact_value_to_u64(value); + u64 i = 0; + if (value.kind == ExactValue_Integer && value.value_integer.sign) { + i = exact_value_to_i64(value); + } else { + i = exact_value_to_u64(value); + } return cg_value(tb_inst_uint(p->func, dt, i), type); } else { i64 i = exact_value_to_i64(value); diff --git a/src/tilde_expr.cpp b/src/tilde_expr.cpp index 6ff912dd9..7097f76ed 100644 --- a/src/tilde_expr.cpp +++ b/src/tilde_expr.cpp @@ -3416,9 +3416,93 @@ gb_internal cgValue cg_build_expr_internal(cgProcedure *p, Ast *expr) { token_pos_to_string(token_pos)); return {}; - } + +gb_internal cgValue cg_map_data_uintptr(cgProcedure *p, cgValue value) { + GB_ASSERT(is_type_map(value.type) || are_types_identical(value.type, t_raw_map)); + cgValue data = cg_emit_struct_ev(p, value, 0); + u64 mask_value = 0; + if (build_context.ptr_size == 4) { + mask_value = 0xfffffffful & ~(MAP_CACHE_LINE_SIZE-1); + } else { + mask_value = 0xffffffffffffffffull & ~(MAP_CACHE_LINE_SIZE-1); + } + cgValue mask = cg_const_int(p, t_uintptr, mask_value); + return cg_emit_arith(p, Token_And, data, mask, t_uintptr); +} + +gb_internal cgValue cg_gen_map_key_hash(cgProcedure *p, cgValue const &map_ptr, cgValue key, cgValue *key_ptr_) { + TEMPORARY_ALLOCATOR_GUARD(); + + cgValue key_ptr = cg_address_from_load_or_generate_local(p, key); + key_ptr = cg_emit_conv(p, key_ptr, t_rawptr); + + if (key_ptr_) *key_ptr_ = key_ptr; + + Type* key_type = base_type(type_deref(map_ptr.type))->Map.key; + + cgValue hasher = cg_hasher_proc_value_for_type(p, key_type); + + Slice args = {}; + args = slice_make(temporary_allocator(), 1); + args[0] = cg_map_data_uintptr(p, cg_emit_load(p, map_ptr)); + cgValue seed = cg_emit_runtime_call(p, "map_seed_from_map_data", args); + + args = slice_make(temporary_allocator(), 2); + args[0] = key_ptr; + args[1] = seed; + return cg_emit_call(p, hasher, args); +} + +gb_internal cgValue cg_internal_dynamic_map_get_ptr(cgProcedure *p, cgValue const &map_ptr, cgValue const &key) { + TEMPORARY_ALLOCATOR_GUARD(); + + Type *map_type = base_type(type_deref(map_ptr.type)); + GB_ASSERT(map_type->kind == Type_Map); + + cgValue ptr = {}; + cgValue key_ptr = {}; + cgValue hash = cg_gen_map_key_hash(p, map_ptr, key, &key_ptr); + + auto args = slice_make(temporary_allocator(), 4); + args[0] = cg_emit_transmute(p, map_ptr, t_raw_map_ptr); + args[1] = cg_builtin_map_info(p, map_type); + args[2] = hash; + args[3] = key_ptr; + + ptr = cg_emit_runtime_call(p, "__dynamic_map_get", args); + + return cg_emit_conv(p, ptr, alloc_type_pointer(map_type->Map.value)); +} + + +gb_internal void cg_internal_dynamic_map_set(cgProcedure *p, cgValue const &map_ptr, Type *map_type, + cgValue const &map_key, cgValue const &map_value, Ast *node) { + TEMPORARY_ALLOCATOR_GUARD(); + + map_type = base_type(map_type); + GB_ASSERT(map_type->kind == Type_Map); + + cgValue key_ptr = {}; + cgValue hash = cg_gen_map_key_hash(p, map_ptr, map_key, &key_ptr); + + cgValue v = cg_emit_conv(p, map_value, map_type->Map.value); + cgValue value_ptr = cg_address_from_load_or_generate_local(p, v); + + auto args = slice_make(temporary_allocator(), 6); + args[0] = cg_emit_conv(p, map_ptr, t_raw_map_ptr); + args[1] = cg_builtin_map_info(p, map_type); + args[2] = hash; + args[3] = cg_emit_conv(p, key_ptr, t_rawptr); + args[4] = cg_emit_conv(p, value_ptr, t_rawptr); + args[5] = cg_emit_source_code_location_as_global(p, node); + cg_emit_runtime_call(p, "__dynamic_map_set", args); +} + + + + gb_internal cgValue cg_build_addr_ptr(cgProcedure *p, Ast *expr) { cgAddr addr = cg_build_addr(p, expr); return cg_addr_get_ptr(p, addr); @@ -3501,17 +3585,16 @@ gb_internal cgAddr cg_build_addr_index_expr(cgProcedure *p, Ast *expr) { GB_ASSERT_MSG(is_type_indexable(t), "%s %s", type_to_string(t), expr_to_string(expr)); if (is_type_map(t)) { - GB_PANIC("TODO(bill): map indexing"); - // lbAddr map_addr = lb_build_addr(p, ie->expr); - // lbValue key = lb_build_expr(p, ie->index); - // key = lb_emit_conv(p, key, t->Map.key); + cgAddr map_addr = cg_build_addr(p, ie->expr); + cgValue key = cg_build_expr(p, ie->index); + key = cg_emit_conv(p, key, t->Map.key); - // Type *result_type = type_of_expr(expr); - // lbValue map_ptr = lb_addr_get_ptr(p, map_addr); - // if (is_type_pointer(type_deref(map_ptr.type))) { - // map_ptr = lb_emit_load(p, map_ptr); - // } - // return lb_addr_map(map_ptr, key, t, result_type); + Type *result_type = type_of_expr(expr); + cgValue map_ptr = cg_addr_get_ptr(p, map_addr); + if (is_type_pointer(type_deref(map_ptr.type))) { + map_ptr = cg_emit_load(p, map_ptr); + } + return cg_addr_map(map_ptr, key, t, result_type); } switch (t->kind) { diff --git a/src/tilde_proc.cpp b/src/tilde_proc.cpp index 1981d32ce..26d1ce409 100644 --- a/src/tilde_proc.cpp +++ b/src/tilde_proc.cpp @@ -388,7 +388,7 @@ gb_internal WORKER_TASK_PROC(cg_procedure_compile_worker_proc) { // emit ir if ( - // string_starts_with(p->name, str_lit("bug@main")) || + string_starts_with(p->name, str_lit("main@")) || // p->name == str_lit("runtime@_windows_default_alloc_or_resize") || false ) { // IR Printing @@ -398,6 +398,7 @@ gb_internal WORKER_TASK_PROC(cg_procedure_compile_worker_proc) { tb_pass_print(passes); fprintf(stdout, "\n"); + fflush(stdout); } if (false) { // GraphViz printing tb_function_print(p->func, tb_default_print_callback, stdout); @@ -408,6 +409,7 @@ gb_internal WORKER_TASK_PROC(cg_procedure_compile_worker_proc) { if (emit_asm) { tb_output_print_asm(output, stdout); fprintf(stdout, "\n"); + fflush(stdout); } return 0; @@ -1018,6 +1020,7 @@ gb_internal cgProcedure *cg_equal_proc_for_type(cgModule *m, Type *type) { cgProcedure *p = cg_procedure_create_dummy(m, proc_name, t_equal_proc); map_set(&m->equal_procs, type, p); + p->split_returns_index = 2; cg_procedure_begin(p); @@ -1168,6 +1171,7 @@ gb_internal cgProcedure *cg_hasher_proc_for_type(cgModule *m, Type *type) { cgProcedure *p = cg_procedure_create_dummy(m, proc_name, t_hasher_proc); map_set(&m->hasher_procs, type, p); + p->split_returns_index = 2; cg_procedure_begin(p); defer (cg_procedure_end(p)); diff --git a/src/tilde_stmt.cpp b/src/tilde_stmt.cpp index 2a2aa31aa..c3673fb58 100644 --- a/src/tilde_stmt.cpp +++ b/src/tilde_stmt.cpp @@ -77,7 +77,7 @@ gb_internal void cg_emit_store(cgProcedure *p, cgValue dst, cgValue src, bool is GB_ASSERT(is_type_pointer(dst.type)); Type *dst_type = type_deref(dst.type); - GB_ASSERT_MSG(are_types_identical(dst_type, src.type), "%s vs %s", type_to_string(dst_type), type_to_string(src.type)); + GB_ASSERT_MSG(are_types_identical(core_type(dst_type), core_type(src.type)), "%s vs %s", type_to_string(dst_type), type_to_string(src.type)); TB_DataType dt = cg_data_type(dst_type); TB_DataType st = cg_data_type(src.type); @@ -225,6 +225,35 @@ gb_internal cgValue cg_addr_load(cgProcedure *p, cgAddr addr) { switch (addr.kind) { case cgAddr_Default: return cg_emit_load(p, addr.addr); + + case cgAddr_Map: + { + Type *map_type = base_type(type_deref(addr.addr.type)); + GB_ASSERT(map_type->kind == Type_Map); + cgAddr v_addr = cg_add_local(p, map_type->Map.value, nullptr, true); + + cgValue ptr = cg_internal_dynamic_map_get_ptr(p, addr.addr, addr.map.key); + cgValue ok = cg_emit_conv(p, cg_emit_comp_against_nil(p, Token_NotEq, ptr), t_bool); + + TB_Node *then = cg_control_region(p, "map.get.then"); + TB_Node *done = cg_control_region(p, "map.get.done"); + cg_emit_if(p, ok, then, done); + tb_inst_set_control(p->func, then); + { + cgValue value = cg_emit_conv(p, ptr, alloc_type_pointer(map_type->Map.value)); + value = cg_emit_load(p, value); + cg_addr_store(p, v_addr, value); + } + cg_emit_goto(p, done); + tb_inst_set_control(p->func, done); + + cgValue v = cg_addr_load(p, v_addr); + if (is_type_tuple(addr.map.result)) { + return cg_value_multi2(v, ok, addr.map.result); + } else { + return v; + } + } } GB_PANIC("TODO(bill): cg_addr_load %p", addr.addr.node); return {}; @@ -254,7 +283,8 @@ gb_internal void cg_addr_store(cgProcedure *p, cgAddr addr, cgValue value) { } else if (addr.kind == cgAddr_RelativeSlice) { GB_PANIC("TODO(bill): cgAddr_RelativeSlice"); } else if (addr.kind == cgAddr_Map) { - GB_PANIC("TODO(bill): cgAddr_Map"); + cg_internal_dynamic_map_set(p, addr.addr, addr.map.type, addr.map.key, value, p->curr_stmt); + return; } else if (addr.kind == cgAddr_Context) { cgAddr old_addr = cg_find_or_generate_context_ptr(p); @@ -1096,7 +1126,7 @@ gb_internal void cg_build_return_stmt_internal(cgProcedure *p, Slice co } } else { - GB_ASSERT(!is_calling_convention_odin(p->type->Proc.calling_convention)); + GB_ASSERT_MSG(!is_calling_convention_odin(p->type->Proc.calling_convention), "missing %s", proc_calling_convention_strings[p->type->Proc.calling_convention]); if (p->return_by_ptr) { Entity *e = tuple->variables[return_count-1]; diff --git a/src/tilde_type_info.cpp b/src/tilde_type_info.cpp index 16fe5fd3e..54441c0d4 100644 --- a/src/tilde_type_info.cpp +++ b/src/tilde_type_info.cpp @@ -332,14 +332,14 @@ gb_internal void cg_setup_type_info_data(cgModule *m) { char const *name = CG_TYPE_INFO_TYPES_NAME; Type *t = alloc_type_array(t_type_info_ptr, count); TB_Global *g = tb_global_create(m->mod, -1, name, nullptr, TB_LINKAGE_PRIVATE); - tb_global_set_storage(m->mod, tb_module_get_rdata(m->mod), g, type_size_of(t), 16, count*2); + tb_global_set_storage(m->mod, tb_module_get_rdata(m->mod), g, type_size_of(t), 16, count*3); cg_global_type_info_member_types = GlobalTypeInfoData{g, t, t_type_info_ptr, 0}; } { char const *name = CG_TYPE_INFO_NAMES_NAME; Type *t = alloc_type_array(t_string, count); TB_Global *g = tb_global_create(m->mod, -1, name, nullptr, TB_LINKAGE_PRIVATE); - tb_global_set_storage(m->mod, tb_module_get_rdata(m->mod), g, type_size_of(t), 16, count*2); + tb_global_set_storage(m->mod, tb_module_get_rdata(m->mod), g, type_size_of(t), 16, count*3); cg_global_type_info_member_names = GlobalTypeInfoData{g, t, t_string, 0}; } { @@ -362,7 +362,7 @@ gb_internal void cg_setup_type_info_data(cgModule *m) { char const *name = CG_TYPE_INFO_TAGS_NAME; Type *t = alloc_type_array(t_string, count); TB_Global *g = tb_global_create(m->mod, -1, name, nullptr, TB_LINKAGE_PRIVATE); - tb_global_set_storage(m->mod, tb_module_get_rdata(m->mod), g, type_size_of(t), 16, count*2); + tb_global_set_storage(m->mod, tb_module_get_rdata(m->mod), g, type_size_of(t), 16, count*3); cg_global_type_info_member_tags = GlobalTypeInfoData{g, t, t_string, 0}; } }