| author | |
| committer | |
| log | d3ebd428650748e60db70dd2171cc044855814b1 |
| tree | 877fd512be87b46adf73c8143d0c721e9d9849c0 |
| parent | 3b26e508638e96997aff3d5ef2c42a917bf86cc8 |
This commit makes it possible to obtain pointers to `extern` variables
at comptime.
- `ir_get_var_ptr` employs several checks to determine if the given
variable is eligible for obtaining its pointer at comptime. This
commit alters these checks to consider `extern` variables, which have
runtime values, as eligible.
- After this change, it's now possible for `render_const_val` to be
called for `extern` variables. This commit modifies
`render_const_val` to suppress the value generation for `extern`
variables.
- `do_code_gen` now creates `ZigValue::llvm_global` of `extern`
variables before iterating through module-level variables so that
other module-level variables can refer to them.
This solution is incomplete since there are several cases still
failing:
- `global_var.array[n..m]`
- `&global_var.array[i]`
- `&global_var.inner_struct.value`
- `&global_array[i]`
Closes #53498 files changed, 284 insertions(+), 47 deletions(-)
src/codegen.cpp+50-20| ... | ... | @@ -7754,6 +7754,13 @@ static LLVMValueRef gen_const_val(CodeGen *g, ZigValue *const_val, const char *n |
| 7754 | 7754 | } |
| 7755 | 7755 | |
| 7756 | 7756 | static void render_const_val(CodeGen *g, ZigValue *const_val, const char *name) { |
| 7757 | if (const_val->special == ConstValSpecialRuntime) { | |
| 7758 | // `const_val` refers to an extern variable. Don't generate an `LLVMValueRef` for | |
| 7759 | // the variable. We shouldn't call `LLVMSetInitializer` on it either. | |
| 7760 | assert(const_val->llvm_global); | |
| 7761 | return; | |
| 7762 | } | |
| 7763 | ||
| 7757 | 7764 | if (!const_val->llvm_value) |
| 7758 | 7765 | const_val->llvm_value = gen_const_val(g, const_val, name); |
| 7759 | 7766 | |
| ... | ... | @@ -7762,6 +7769,13 @@ static void render_const_val(CodeGen *g, ZigValue *const_val, const char *name) |
| 7762 | 7769 | } |
| 7763 | 7770 | |
| 7764 | 7771 | static void render_const_val_global(CodeGen *g, ZigValue *const_val, const char *name) { |
| 7772 | if (const_val->special == ConstValSpecialRuntime) { | |
| 7773 | // `const_val` refers to an extern variable. `llvm_global` should already | |
| 7774 | // have been created by an earlier codegen pass. | |
| 7775 | assert(const_val->llvm_global); | |
| 7776 | return; | |
| 7777 | } | |
| 7778 | ||
| 7765 | 7779 | if (!const_val->llvm_global) { |
| 7766 | 7780 | LLVMTypeRef type_ref = const_val->llvm_value ? |
| 7767 | 7781 | LLVMTypeOf(const_val->llvm_value) : get_llvm_type(g, const_val->type); |
| ... | ... | @@ -7891,6 +7905,39 @@ static void do_code_gen(CodeGen *g) { |
| 7891 | 7905 | |
| 7892 | 7906 | generate_error_name_table(g); |
| 7893 | 7907 | |
| 7908 | // Create extern variables | |
| 7909 | for (size_t i = 0; i < g->global_vars.length; i += 1) { | |
| 7910 | TldVar *tld_var = g->global_vars.at(i); | |
| 7911 | ZigVar *var = tld_var->var; | |
| 7912 | ||
| 7913 | bool externally_initialized = var->decl_node->data.variable_declaration.expr == nullptr; | |
| 7914 | if (!externally_initialized) { | |
| 7915 | continue; | |
| 7916 | } | |
| 7917 | ||
| 7918 | assert(var->decl_node->data.variable_declaration.is_extern); | |
| 7919 | const char *symbol_name = var->name; | |
| 7920 | ||
| 7921 | LLVMValueRef global_value; | |
| 7922 | LLVMValueRef existing_llvm_var = LLVMGetNamedGlobal(g->module, symbol_name); | |
| 7923 | if (existing_llvm_var) { | |
| 7924 | global_value = LLVMConstBitCast(existing_llvm_var, | |
| 7925 | LLVMPointerType(get_llvm_type(g, var->var_type), 0)); | |
| 7926 | } else { | |
| 7927 | global_value = LLVMAddGlobal(g->module, get_llvm_type(g, var->var_type), symbol_name); | |
| 7928 | // TODO debug info for the extern variable | |
| 7929 | ||
| 7930 | LLVMSetLinkage(global_value, LLVMExternalLinkage); | |
| 7931 | maybe_import_dll(g, global_value, GlobalLinkageIdStrong); | |
| 7932 | LLVMSetAlignment(global_value, var->align_bytes); | |
| 7933 | LLVMSetGlobalConstant(global_value, var->gen_is_const); | |
| 7934 | set_global_tls(g, var, global_value); | |
| 7935 | } | |
| 7936 | ||
| 7937 | var->value_ref = global_value; | |
| 7938 | var->const_value->llvm_global = global_value; | |
| 7939 | } | |
| 7940 | ||
| 7894 | 7941 | // Generate module level variables |
| 7895 | 7942 | for (size_t i = 0; i < g->global_vars.length; i += 1) { |
| 7896 | 7943 | TldVar *tld_var = g->global_vars.at(i); |
| ... | ... | @@ -7956,28 +8003,12 @@ static void do_code_gen(CodeGen *g) { |
| 7956 | 8003 | linkage = global_export->linkage; |
| 7957 | 8004 | } |
| 7958 | 8005 | |
| 7959 | LLVMValueRef global_value; | |
| 7960 | 8006 | bool externally_initialized = var->decl_node->data.variable_declaration.expr == nullptr; |
| 7961 | if (externally_initialized) { | |
| 7962 | LLVMValueRef existing_llvm_var = LLVMGetNamedGlobal(g->module, symbol_name); | |
| 7963 | if (existing_llvm_var) { | |
| 7964 | global_value = LLVMConstBitCast(existing_llvm_var, | |
| 7965 | LLVMPointerType(get_llvm_type(g, var->var_type), 0)); | |
| 7966 | } else { | |
| 7967 | global_value = LLVMAddGlobal(g->module, get_llvm_type(g, var->var_type), symbol_name); | |
| 7968 | // TODO debug info for the extern variable | |
| 7969 | ||
| 7970 | LLVMSetLinkage(global_value, to_llvm_linkage(linkage)); | |
| 7971 | maybe_import_dll(g, global_value, GlobalLinkageIdStrong); | |
| 7972 | LLVMSetAlignment(global_value, var->align_bytes); | |
| 7973 | LLVMSetGlobalConstant(global_value, var->gen_is_const); | |
| 7974 | set_global_tls(g, var, global_value); | |
| 7975 | } | |
| 7976 | } else { | |
| 8007 | if (!externally_initialized) { | |
| 7977 | 8008 | bool exported = (linkage != GlobalLinkageIdInternal); |
| 7978 | 8009 | render_const_val(g, var->const_value, symbol_name); |
| 7979 | 8010 | render_const_val_global(g, var->const_value, symbol_name); |
| 7980 | global_value = var->const_value->llvm_global; | |
| 8011 | LLVMValueRef global_value = var->const_value->llvm_global; | |
| 7981 | 8012 | |
| 7982 | 8013 | if (exported) { |
| 7983 | 8014 | LLVMSetLinkage(global_value, to_llvm_linkage(linkage)); |
| ... | ... | @@ -7997,10 +8028,9 @@ static void do_code_gen(CodeGen *g) { |
| 7997 | 8028 | |
| 7998 | 8029 | LLVMSetGlobalConstant(global_value, var->gen_is_const); |
| 7999 | 8030 | set_global_tls(g, var, global_value); |
| 8031 | var->value_ref = global_value; | |
| 8000 | 8032 | } |
| 8001 | 8033 | |
| 8002 | var->value_ref = global_value; | |
| 8003 | ||
| 8004 | 8034 | for (size_t export_i = 1; export_i < var->export_list.length; export_i += 1) { |
| 8005 | 8035 | GlobalExport *global_export = &var->export_list.items[export_i]; |
| 8006 | 8036 | LLVMAddAlias(g->module, LLVMTypeOf(var->value_ref), var->value_ref, buf_ptr(&global_export->name)); |
src/ir.cpp+34-27| ... | ... | @@ -19894,30 +19894,34 @@ static IrInstGen *ir_get_var_ptr(IrAnalyze *ira, IrInst *source_instr, ZigVar *v |
| 19894 | 19894 | IrInstGen *result = ir_build_var_ptr_gen(ira, source_instr, var); |
| 19895 | 19895 | result->value->type = var_ptr_type; |
| 19896 | 19896 | |
| 19897 | if (!linkage_makes_it_runtime && !var->is_thread_local && value_is_comptime(var->const_value)) { | |
| 19897 | bool is_local_var = !var->decl_node->data.variable_declaration.is_extern && | |
| 19898 | var->const_value->special == ConstValSpecialRuntime; | |
| 19899 | ||
| 19900 | // The address of a thread-local variable can't be resolved even by a linker because | |
| 19901 | // it's dependent on the current thread. The concept of current thread doesn't exist | |
| 19902 | // at compile time, so even if we had a symbolic (i.e., relocatable) representation | |
| 19903 | // of a pointer to a thread-local variable, there would be no ways to make use of it | |
| 19904 | // in a meaningful way. | |
| 19905 | // | |
| 19906 | // The same goes for local variables - They are stored in a stack frame, whose | |
| 19907 | // instance doesn't even exist at compile/link time. | |
| 19908 | if (!var->is_thread_local && !is_local_var) { | |
| 19898 | 19909 | ZigValue *val = var->const_value; |
| 19899 | switch (val->special) { | |
| 19900 | case ConstValSpecialRuntime: | |
| 19901 | break; | |
| 19902 | case ConstValSpecialStatic: // fallthrough | |
| 19903 | case ConstValSpecialLazy: // fallthrough | |
| 19904 | case ConstValSpecialUndef: { | |
| 19905 | ConstPtrMut ptr_mut; | |
| 19906 | if (comptime_var_mem) { | |
| 19907 | ptr_mut = ConstPtrMutComptimeVar; | |
| 19908 | } else if (var->gen_is_const) { | |
| 19909 | ptr_mut = ConstPtrMutComptimeConst; | |
| 19910 | } else { | |
| 19911 | assert(!comptime_var_mem); | |
| 19912 | ptr_mut = ConstPtrMutRuntimeVar; | |
| 19913 | } | |
| 19914 | result->value->special = ConstValSpecialStatic; | |
| 19915 | result->value->data.x_ptr.mut = ptr_mut; | |
| 19916 | result->value->data.x_ptr.special = ConstPtrSpecialRef; | |
| 19917 | result->value->data.x_ptr.data.ref.pointee = val; | |
| 19918 | return result; | |
| 19919 | } | |
| 19910 | ||
| 19911 | ConstPtrMut ptr_mut; | |
| 19912 | if (comptime_var_mem) { | |
| 19913 | ptr_mut = ConstPtrMutComptimeVar; | |
| 19914 | } else if (var->gen_is_const && !linkage_makes_it_runtime) { | |
| 19915 | ptr_mut = ConstPtrMutComptimeConst; | |
| 19916 | } else { | |
| 19917 | assert(!comptime_var_mem); | |
| 19918 | ptr_mut = ConstPtrMutRuntimeVar; | |
| 19920 | 19919 | } |
| 19920 | result->value->special = ConstValSpecialStatic; | |
| 19921 | result->value->data.x_ptr.mut = ptr_mut; | |
| 19922 | result->value->data.x_ptr.special = ConstPtrSpecialRef; | |
| 19923 | result->value->data.x_ptr.data.ref.pointee = val; | |
| 19924 | return result; | |
| 19921 | 19925 | } |
| 19922 | 19926 | |
| 19923 | 19927 | bool in_fn_scope = (scope_fn_entry(var->parent_scope) != nullptr); |
| ... | ... | @@ -22288,12 +22292,15 @@ static IrInstGen *ir_analyze_struct_field_ptr(IrAnalyze *ira, IrInst* source_ins |
| 22288 | 22292 | if (type_is_invalid(struct_val->type)) |
| 22289 | 22293 | return ira->codegen->invalid_inst_gen; |
| 22290 | 22294 | |
| 22291 | // This to allow lazy values to be resolved. | |
| 22292 | if ((err = ir_resolve_const_val(ira->codegen, ira->new_irb.exec, | |
| 22293 | source_instr->source_node, struct_val, UndefOk))) | |
| 22294 | { | |
| 22295 | return ira->codegen->invalid_inst_gen; | |
| 22295 | if (ptr_val->data.x_ptr.mut != ConstPtrMutRuntimeVar) { | |
| 22296 | // This to allow lazy values to be resolved. | |
| 22297 | if ((err = ir_resolve_const_val(ira->codegen, ira->new_irb.exec, | |
| 22298 | source_instr->source_node, struct_val, UndefOk))) | |
| 22299 | { | |
| 22300 | return ira->codegen->invalid_inst_gen; | |
| 22301 | } | |
| 22296 | 22302 | } |
| 22303 | ||
| 22297 | 22304 | if (initializing && struct_val->special == ConstValSpecialUndef) { |
| 22298 | 22305 | struct_val->data.x_struct.fields = alloc_const_vals_ptrs(ira->codegen, struct_type->data.structure.src_field_count); |
| 22299 | 22306 | struct_val->special = ConstValSpecialStatic; |
test/compile_errors.zig+22| ... | ... | @@ -7637,4 +7637,26 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { |
| 7637 | 7637 | , &[_][]const u8{ |
| 7638 | 7638 | "tmp.zig:4:9: error: expected type '*c_void', found '?*c_void'", |
| 7639 | 7639 | }); |
| 7640 | ||
| 7641 | cases.add("pointer to a local runtime `var` is not constant", | |
| 7642 | \\export fn get_ptr() *const u32 { | |
| 7643 | \\ var local_var: u32 = 42; | |
| 7644 | \\ return struct { | |
| 7645 | \\ const ptr = &local_var; | |
| 7646 | \\ }.ptr; | |
| 7647 | \\} | |
| 7648 | , &[_][]const u8{ | |
| 7649 | ":4:21: error: cannot store runtime value in compile time variable", | |
| 7650 | }); | |
| 7651 | ||
| 7652 | cases.add("pointer to a local runtime `const` is not constant", | |
| 7653 | \\export fn get_ptr(x: u32) *const u32 { | |
| 7654 | \\ const local_var: u32 = x; | |
| 7655 | \\ return struct { | |
| 7656 | \\ const ptr = &local_var; | |
| 7657 | \\ }.ptr; | |
| 7658 | \\} | |
| 7659 | , &[_][]const u8{ | |
| 7660 | ":4:21: error: cannot store runtime value in compile time variable", | |
| 7661 | }); | |
| 7640 | 7662 | } |
test/standalone.zig+1| ... | ... | @@ -19,6 +19,7 @@ pub fn addCases(cases: *tests.StandaloneContext) void { |
| 19 | 19 | cases.addBuildFile("test/standalone/use_alias/build.zig"); |
| 20 | 20 | cases.addBuildFile("test/standalone/brace_expansion/build.zig"); |
| 21 | 21 | cases.addBuildFile("test/standalone/empty_env/build.zig"); |
| 22 | cases.addBuildFile("test/standalone/extern_ref/build.zig"); | |
| 22 | 23 | if (std.Target.current.os.tag != .wasi) { |
| 23 | 24 | cases.addBuildFile("test/standalone/load_dynamic_library/build.zig"); |
| 24 | 25 | } |
test/standalone/extern_ref/build.zig created+15| ... | ... | @@ -0,0 +1,15 @@ |
| 1 | const Builder = @import("std").build.Builder; | |
| 2 | ||
| 3 | pub fn build(b: *Builder) void { | |
| 4 | const mode = b.standardReleaseOptions(); | |
| 5 | ||
| 6 | const obj = b.addStaticLibrary("obj", "obj.zig"); | |
| 7 | obj.setBuildMode(mode); | |
| 8 | ||
| 9 | const main = b.addTest("main.zig"); | |
| 10 | main.setBuildMode(mode); | |
| 11 | main.linkLibrary(obj); | |
| 12 | ||
| 13 | const test_step = b.step("test", "Test it"); | |
| 14 | test_step.dependOn(&main.step); | |
| 15 | } |
test/standalone/extern_ref/main.zig created+120| ... | ... | @@ -0,0 +1,120 @@ |
| 1 | const std = @import("std"); | |
| 2 | const eql = std.mem.eql; | |
| 3 | ||
| 4 | // These are defined in `obj.zig` | |
| 5 | extern var global_var: usize; | |
| 6 | extern const global_const: usize; | |
| 7 | ||
| 8 | const TheStruct = @import("./types.zig").TheStruct; | |
| 9 | extern var global_var_struct: TheStruct; | |
| 10 | extern const global_const_struct: TheStruct; | |
| 11 | ||
| 12 | const TheUnion = @import("./types.zig").TheUnion; | |
| 13 | extern var global_var_union: TheUnion; | |
| 14 | extern const global_const_union: TheUnion; | |
| 15 | ||
| 16 | extern var global_var_array: [4]u32; | |
| 17 | extern const global_const_array: [4]u32; | |
| 18 | ||
| 19 | // Take the pointers to external entities as constant values | |
| 20 | const p_global_var = &global_var; | |
| 21 | const p_global_const = &global_const; | |
| 22 | ||
| 23 | test "access the external integers" { | |
| 24 | std.testing.expect(p_global_var.* == 2); | |
| 25 | std.testing.expect(p_global_const.* == 422); | |
| 26 | } | |
| 27 | ||
| 28 | const p_global_var_struct = &global_var_struct; | |
| 29 | const p_global_const_struct = &global_const_struct; | |
| 30 | ||
| 31 | const p_global_var_struct_val = &global_var_struct.value; | |
| 32 | const p_global_const_struct_val = &global_const_struct.value; | |
| 33 | ||
| 34 | const p_global_var_struct_array = &global_var_struct.array; | |
| 35 | const p_global_const_struct_array = &global_const_struct.array; | |
| 36 | ||
| 37 | const p_global_var_struct_array2 = global_var_struct.array[1..3]; | |
| 38 | const p_global_const_struct_array2 = global_const_struct.array[1..3]; | |
| 39 | ||
| 40 | const p_global_var_struct_array3 = &global_var_struct.array[1]; | |
| 41 | const p_global_const_struct_array3 = &global_const_struct.array[1]; | |
| 42 | ||
| 43 | test "access the external integers in a struct through comptime ptrs" { | |
| 44 | std.testing.expect(p_global_var_struct.value == 2); | |
| 45 | std.testing.expect(p_global_const_struct.value == 422); | |
| 46 | ||
| 47 | std.testing.expect(p_global_var_struct_val.* == 2); | |
| 48 | std.testing.expect(p_global_const_struct_val.* == 422); | |
| 49 | } | |
| 50 | ||
| 51 | test "access the external arrays in a struct through comptime ptrs" { | |
| 52 | // TODO | |
| 53 | // std.testing.expect(eql(u32, &p_global_var_struct.array, &[_]u32{1, 2, 3, 4})); | |
| 54 | // std.testing.expect(eql(u32, &p_global_const_struct.array, &[_]u32{5, 6, 7, 8})); | |
| 55 | ||
| 56 | // TODO | |
| 57 | // std.testing.expect(eql(u32, p_global_var_struct_array, &[_]u32{1, 2, 3, 4})); | |
| 58 | // std.testing.expect(eql(u32, p_global_const_struct_array, &[_]u32{5, 6, 7, 8})); | |
| 59 | ||
| 60 | // TODO | |
| 61 | // std.testing.expect(eql(u32, p_global_var_struct_array2, &[_]u32{2, 3})); | |
| 62 | // std.testing.expect(eql(u32, p_global_const_struct_array2, &[_]u32{6, 7})); | |
| 63 | ||
| 64 | // TODO | |
| 65 | // std.testing.expect(p_global_var_struct_array3.* == 2); | |
| 66 | // std.testing.expect(p_global_const_struct_array3.* == 6); | |
| 67 | } | |
| 68 | ||
| 69 | test "access the external integers with indirection through comptime ptrs" { | |
| 70 | std.testing.expect(p_global_var_struct.p_value.* == 3); | |
| 71 | std.testing.expect(p_global_const_struct.p_value.* == 423); | |
| 72 | } | |
| 73 | ||
| 74 | const p_global_var_struct_inner_val = &global_var_struct.inner.value; | |
| 75 | const p_global_const_struct_inner_val = &global_const_struct.inner.value; | |
| 76 | ||
| 77 | test "access the external integers in a nested struct through comptime ptrs" { | |
| 78 | // TODO | |
| 79 | // std.testing.expect(p_global_var_struct_inner_val.* == 4); | |
| 80 | // std.testing.expect(p_global_const_struct_inner_val.* == 424); | |
| 81 | } | |
| 82 | ||
| 83 | const p_global_var_union = &global_var_union; | |
| 84 | const p_global_const_union = &global_const_union; | |
| 85 | ||
| 86 | const p_global_var_union_val = &global_var_union.U32; | |
| 87 | const p_global_const_union_val = &global_const_union.U32; | |
| 88 | ||
| 89 | test "access the external integers in a union through comptime ptrs" { | |
| 90 | std.testing.expect(p_global_var_union.U32 == 10); | |
| 91 | std.testing.expect(p_global_const_union.U32 == 20); | |
| 92 | ||
| 93 | // TODO | |
| 94 | // std.testing.expect(p_global_var_union_val.* == 10); | |
| 95 | // std.testing.expect(p_global_const_union_val.* == 20); | |
| 96 | } | |
| 97 | ||
| 98 | const p_global_var_array = &global_var_array; | |
| 99 | const p_global_const_array = &global_const_array; | |
| 100 | ||
| 101 | const p_global_var_array2 = global_var_array[1..3]; | |
| 102 | const p_global_const_array2 = global_const_array[1..3]; | |
| 103 | ||
| 104 | const p_global_var_array3 = &global_var_array[1]; | |
| 105 | const p_global_const_array3 = &global_const_array[1]; | |
| 106 | ||
| 107 | test "access the external arrays through comptime ptrs" { | |
| 108 | std.testing.expect(eql(u32, &global_var_array, &[_]u32{1, 2, 3, 4})); | |
| 109 | std.testing.expect(eql(u32, &global_const_array, &[_]u32{5, 6, 7, 8})); | |
| 110 | ||
| 111 | std.testing.expect(eql(u32, p_global_var_array, &[_]u32{1, 2, 3, 4})); | |
| 112 | std.testing.expect(eql(u32, p_global_const_array, &[_]u32{5, 6, 7, 8})); | |
| 113 | ||
| 114 | std.testing.expect(eql(u32, p_global_var_array2, &[_]u32{2, 3})); | |
| 115 | std.testing.expect(eql(u32, p_global_const_array2, &[_]u32{6, 7})); | |
| 116 | ||
| 117 | // TODO | |
| 118 | // std.testing.expect(p_global_var_array3.* == 2); | |
| 119 | // std.testing.expect(p_global_const_array3.* == 6); | |
| 120 | } |
test/standalone/extern_ref/obj.zig created+27| ... | ... | @@ -0,0 +1,27 @@ |
| 1 | export var global_var: usize = 2; | |
| 2 | export const global_const: usize = 422; | |
| 3 | ||
| 4 | const TheStruct = @import("./types.zig").TheStruct; | |
| 5 | export var global_var_struct = TheStruct{ | |
| 6 | .value = 2, | |
| 7 | .array = [_]u32{ 1, 2, 3, 4 }, | |
| 8 | .p_value = &@as(u32, 3), | |
| 9 | .inner = .{ .value = 4 }, | |
| 10 | }; | |
| 11 | export const global_const_struct = TheStruct{ | |
| 12 | .value = 422, | |
| 13 | .array = [_]u32{ 5, 6, 7, 8 }, | |
| 14 | .p_value = &@as(u32, 423), | |
| 15 | .inner = .{ .value = 424 }, | |
| 16 | }; | |
| 17 | ||
| 18 | const TheUnion = @import("./types.zig").TheUnion; | |
| 19 | export var global_var_union = TheUnion{ | |
| 20 | .U32 = 10, | |
| 21 | }; | |
| 22 | export const global_const_union = TheUnion{ | |
| 23 | .U32 = 20, | |
| 24 | }; | |
| 25 | ||
| 26 | export var global_var_array = [4]u32{ 1, 2, 3, 4 }; | |
| 27 | export const global_const_array = [4]u32{ 5, 6, 7, 8 }; |
test/standalone/extern_ref/types.zig created+15| ... | ... | @@ -0,0 +1,15 @@ |
| 1 | pub const TheStruct = extern struct { | |
| 2 | value: u32, | |
| 3 | array: [4]u32, | |
| 4 | p_value: *const u32, | |
| 5 | inner: InnerStruct, | |
| 6 | }; | |
| 7 | ||
| 8 | pub const InnerStruct = extern struct { | |
| 9 | value: u32, | |
| 10 | }; | |
| 11 | ||
| 12 | pub const TheUnion = extern union { | |
| 13 | U32: u32, | |
| 14 | Bool: bool, | |
| 15 | }; |