Implement globals

This commit is contained in:
David Gonzalez Martin 2024-06-06 18:55:13 -06:00
parent 62cf76b1d7
commit c504cbdc73
3 changed files with 301 additions and 97 deletions

View File

@ -1,4 +1,3 @@
globals
arrays arrays
assert assert
c abi c abi
@ -6,7 +5,6 @@ orelse
size size
trailing zeroes trailing zeroes
leading zeroes leading zeroes
while true
returns inside loops (if conditional) returns inside loops (if conditional)
returns inside loops (else conditional) returns inside loops (else conditional)
returns inside loops (non-conditional) returns inside loops (non-conditional)

View File

@ -88,10 +88,20 @@ const Side = enum {
const GlobalSymbol = struct{ const GlobalSymbol = struct{
attributes: Attributes = .{}, attributes: Attributes = .{},
global_declaration: GlobalDeclaration, global_declaration: GlobalDeclaration,
alignment: u32,
value: Value,
id: GlobalSymbol.Id,
const Id = enum{
function_declaration,
function_definition,
global_variable,
};
const Attributes = struct{ const Attributes = struct{
@"export": bool = false, @"export": bool = false,
@"extern": bool = false, @"extern": bool = false,
mutability: Mutability = .@"var",
}; };
const Attribute = enum{ const Attribute = enum{
@"export", @"export",
@ -99,6 +109,28 @@ const GlobalSymbol = struct{
const Mask = std.EnumSet(Attribute); const Mask = std.EnumSet(Attribute);
}; };
const id_to_global_symbol_map = std.EnumArray(Id, type).init(.{
.function_declaration = Function.Declaration,
.function_definition = Function,
.global_variable = GlobalVariable,
});
fn get_payload(global_symbol: *GlobalSymbol, comptime id: Id) *id_to_global_symbol_map.get(id) {
if (id == .function_definition) {
const function_declaration: *Function.Declaration = @alignCast(@fieldParentPtr("global_symbol", global_symbol));
const function_definition: *Function = @alignCast(@fieldParentPtr("declaration", function_declaration));
return function_definition;
}
return @fieldParentPtr("global_symbol", global_symbol);
}
};
const GlobalVariable = struct {
global_symbol: GlobalSymbol,
type: *Type,
initial_value: *Value,
}; };
const Mutability = enum(u1){ const Mutability = enum(u1){
@ -492,10 +524,13 @@ const Parser = struct{
switch (lookup_result.declaration.*.id) { switch (lookup_result.declaration.*.id) {
.local => unreachable, .local => unreachable,
.global => { .global => {
const global = lookup_result.declaration.*.get_payload(.global); const global_declaration = lookup_result.declaration.*.get_payload(.global);
switch (global.id) { switch (global_declaration.id) {
.global_symbol => {
const global_symbol = global_declaration.to_symbol();
switch (global_symbol.id) {
.function_definition => { .function_definition => {
const function_definition = global.get_payload(.function_definition); const function_definition = global_symbol.get_payload(.function_definition);
const declaration_argument_count = function_definition.declaration.argument_types.len; const declaration_argument_count = function_definition.declaration.argument_types.len;
var argument_values = PinnedArray(*Value){}; var argument_values = PinnedArray(*Value){};
while (true) { while (true) {
@ -535,7 +570,7 @@ const Parser = struct{
}, },
.id = .call, .id = .call,
}, },
.callable = &function_definition.declaration.value, .callable = &function_definition.declaration.global_symbol.value,
.arguments = argument_values.const_slice(), .arguments = argument_values.const_slice(),
}); });
_ = analyzer.current_basic_block.instructions.append(&call.instruction); _ = analyzer.current_basic_block.instructions.append(&call.instruction);
@ -543,6 +578,10 @@ const Parser = struct{
}, },
else => |t| @panic(@tagName(t)), else => |t| @panic(@tagName(t)),
} }
unreachable;
},
else => |t| @panic(@tagName(t)),
}
}, },
.argument => unreachable, .argument => unreachable,
} }
@ -666,9 +705,45 @@ const Parser = struct{
_ = analyzer.current_basic_block.instructions.append(&load.instruction); _ = analyzer.current_basic_block.instructions.append(&load.instruction);
break :block &load.instruction.value; break :block &load.instruction.value;
}, },
.global => block: {
const global_declaration = lookup_result.declaration.*.get_payload(.global);
const global_symbol = global_declaration.to_symbol();
const global_type = switch (global_symbol.id) {
.global_variable => b: {
const global_variable = global_symbol.get_payload(.global_variable);
break :b global_variable.type;
},
else =>|t| @panic(@tagName(t)), else =>|t| @panic(@tagName(t)),
}; };
if (maybe_type) |ty| {
switch (typecheck(ty, global_type)) {
.success => {},
}
}
const load = thread.loads.append(.{
.instruction = .{
.value = .{
.sema = .{
.thread = thread.get_index(),
.resolved = true,
.id = .instruction,
},
},
.id = .load,
},
.value = &global_symbol.value,
.type = global_type,
.alignment = global_type.alignment,
.is_volatile = false,
});
_ = analyzer.current_basic_block.instructions.append(&load.instruction);
break :block &load.instruction.value;
},
};
const declaration_type = declaration_value.get_type(); const declaration_type = declaration_value.get_type();
return switch (unary) { return switch (unary) {
@ -754,6 +829,27 @@ const Parser = struct{
compare_signed_less_equal, compare_signed_less_equal,
}; };
fn parse_constant_expression(parser: *Parser, thread: *Thread, file: *File, maybe_type: ?*Type) *Value {
const src = file.source_code;
const starting_index = parser.i;
const starting_ch = src[starting_index];
const is_digit_start = is_decimal_digit(starting_ch);
const is_alpha_start = is_alphabetic(starting_ch);
_ = is_alpha_start; // autofix
if (is_digit_start) {
const ty = maybe_type orelse exit(1);
switch (ty.sema.id) {
.integer => {
const constant_int = parser.parse_constant_integer(thread, file, ty);
return &constant_int.value;
},
else => unreachable,
}
} else {
unreachable;
}
}
fn parse_expression(parser: *Parser, analyzer: *Analyzer, thread: *Thread, file: *File, ty: ?*Type, side: Side) *Value { fn parse_expression(parser: *Parser, analyzer: *Analyzer, thread: *Thread, file: *File, ty: ?*Type, side: Side) *Value {
const src = file.source_code; const src = file.source_code;
@ -1298,19 +1394,19 @@ const Value = struct {
reserved: u32 = 0, reserved: u32 = 0,
const Id = enum(u8){ const Id = enum(u8){
argument,
basic_block, basic_block,
constant_int, constant_int,
lazy_expression,
instruction, instruction,
function_declaration, global_symbol,
argument, lazy_expression,
}; };
const id_to_value_map = std.EnumArray(Id, type).init(.{ const id_to_value_map = std.EnumArray(Id, type).init(.{
.argument = ArgumentSymbol, .argument = ArgumentSymbol,
.basic_block = BasicBlock, .basic_block = BasicBlock,
.constant_int = ConstantInt, .constant_int = ConstantInt,
.function_declaration = Function.Declaration, .global_symbol = GlobalSymbol,
.instruction = Instruction, .instruction = Instruction,
.lazy_expression = LazyExpression, .lazy_expression = LazyExpression,
}); });
@ -1336,13 +1432,19 @@ const Value = struct {
.call => { .call => {
const call = instruction.get_payload(.call); const call = instruction.get_payload(.call);
switch (call.callable.sema.id) { switch (call.callable.sema.id) {
.function_declaration => { .global_symbol => {
const function_declaration = call.callable.get_payload(.function_declaration); const global_symbol = call.callable.get_payload(.global_symbol);
switch (global_symbol.id) {
.function_definition => {
const function_declaration = global_symbol.get_payload(.function_declaration);
return function_declaration.return_type; return function_declaration.return_type;
}, },
else => |t| @panic(@tagName(t)), else => |t| @panic(@tagName(t)),
} }
}, },
else => |t| @panic(@tagName(t)),
}
},
.integer_compare => { .integer_compare => {
return &instance.threads[value.sema.thread].integers[0].type; return &instance.threads[value.sema.thread].integers[0].type;
}, },
@ -1489,29 +1591,38 @@ const GlobalDeclaration = struct {
id: Id, id: Id,
const Id = enum(u8) { const Id = enum(u8) {
function_definition, // function_definition,
function_declaration, // function_declaration,
file, file,
unresolved_import, unresolved_import,
global_symbol,
// global_variable,
}; };
const id_to_global_declaration_map = std.EnumArray(Id, type).init(.{ const id_to_global_declaration_map = std.EnumArray(Id, type).init(.{
.function_definition = Function, // .function_definition = Function,
.function_declaration = Function.Declaration, // .function_declaration = Function.Declaration,
.file = File, .file = File,
.global_symbol = GlobalSymbol,
// .global_variable = GlobalVariable,
.unresolved_import = Import, .unresolved_import = Import,
}); });
fn get_payload(global_declaration: *GlobalDeclaration, comptime id: Id) *id_to_global_declaration_map.get(id) { fn get_payload(global_declaration: *GlobalDeclaration, comptime id: Id) *id_to_global_declaration_map.get(id) {
assert(global_declaration.id == id); assert(global_declaration.id == id);
// Function definition has to be upcast twice // Function definition has to be upcast twice
if (id == .function_definition) { // if (id == .function_definition) {
const global_symbol: *GlobalSymbol = @alignCast(@fieldParentPtr("global_declaration", global_declaration)); // const global_symbol: *GlobalSymbol = @alignCast(@fieldParentPtr("global_declaration", global_declaration));
const function_declaration: *Function.Declaration = @alignCast(@fieldParentPtr("global_symbol", global_symbol)); // const function_declaration: *Function.Declaration = @alignCast(@fieldParentPtr("global_symbol", global_symbol));
const function_definition: *Function = @alignCast(@fieldParentPtr("declaration", function_declaration)); // const function_definition: *Function = @alignCast(@fieldParentPtr("declaration", function_declaration));
return function_definition; // return function_definition;
// }
return @alignCast(@fieldParentPtr("global_declaration", global_declaration));
} }
fn to_symbol(global_declaration: *GlobalDeclaration) *GlobalSymbol {
assert(global_declaration.id == .global_symbol);
return @alignCast(@fieldParentPtr("global_declaration", global_declaration)); return @alignCast(@fieldParentPtr("global_declaration", global_declaration));
} }
@ -1579,7 +1690,6 @@ const Function = struct{
const Declaration = struct { const Declaration = struct {
attributes: Attributes = .{}, attributes: Attributes = .{},
value: Value,
global_symbol: GlobalSymbol, global_symbol: GlobalSymbol,
return_type: *Type, return_type: *Type,
argument_types: []const *Type = &.{}, argument_types: []const *Type = &.{},
@ -1780,12 +1890,12 @@ const Thread = struct{
local_blocks: PinnedArray(LocalBlock) = .{}, local_blocks: PinnedArray(LocalBlock) = .{},
local_symbols: PinnedArray(LocalSymbol) = .{}, local_symbols: PinnedArray(LocalSymbol) = .{},
argument_symbols: PinnedArray(ArgumentSymbol) = .{}, argument_symbols: PinnedArray(ArgumentSymbol) = .{},
global_variables: PinnedArray(GlobalVariable) = .{},
analyzed_file_count: u32 = 0, analyzed_file_count: u32 = 0,
assigned_file_count: u32 = 0, assigned_file_count: u32 = 0,
llvm: struct { llvm: struct {
context: *LLVM.Context, context: *LLVM.Context,
module: *LLVM.Module, module: *LLVM.Module,
builder: *LLVM.Builder,
attributes: LLVM.Attributes, attributes: LLVM.Attributes,
target_machine: *LLVM.Target.Machine, target_machine: *LLVM.Target.Machine,
object: ?[]const u8 = null, object: ?[]const u8 = null,
@ -2764,23 +2874,29 @@ fn worker_thread(thread_index: u32, cpu_count: *u32) void {
if (file.scope.scope.declarations.get(names[0])) |callable_declaration| { if (file.scope.scope.declarations.get(names[0])) |callable_declaration| {
const global_declaration = callable_declaration.get_payload(.global); const global_declaration = callable_declaration.get_payload(.global);
switch (global_declaration.id) { switch (global_declaration.id) {
.global_symbol => {
const global_symbol = global_declaration.to_symbol();
switch (global_symbol.id) {
.function_definition => { .function_definition => {
const function_definition = global_declaration.get_payload(.function_definition); const function_definition = global_symbol.get_payload(.function_definition);
assert(function_definition.declaration.value.sema.resolved); assert(function_definition.declaration.global_symbol.value.sema.resolved);
assert(function_definition.declaration.value.sema.resolved); assert(function_definition.declaration.global_symbol.value.sema.resolved);
assert(function_definition.declaration.return_type.sema.thread == thread.get_index()); assert(function_definition.declaration.return_type.sema.thread == thread.get_index());
// TODO: here we are duplicating the function declaration, but not the types. It could be interesting to duplicate the types so in the LLVM IR no special case has to take place to deduplicate work done in different threads // TODO: here we are duplicating the function declaration, but not the types. It could be interesting to duplicate the types so in the LLVM IR no special case has to take place to deduplicate work done in different threads
const external_fn = thread.external_functions.append(function_definition.declaration); const external_fn = thread.external_functions.append(function_definition.declaration);
external_fn.global_symbol.attributes.@"export" = false; external_fn.global_symbol.attributes.@"export" = false;
external_fn.global_symbol.attributes.@"extern" = true; external_fn.global_symbol.attributes.@"extern" = true;
external_fn.value.sema.thread = thread.get_index(); external_fn.global_symbol.value.sema.thread = thread.get_index();
external_fn.value.llvm = null; external_fn.global_symbol.value.llvm = null;
call.callable = &external_fn.value; call.callable = &external_fn.global_symbol.value;
value.sema.resolved = true; value.sema.resolved = true;
}, },
else => |t| @panic(@tagName(t)), else => |t| @panic(@tagName(t)),
} }
},
else => |t| @panic(@tagName(t)),
}
} else { } else {
unreachable; unreachable;
} }
@ -2905,12 +3021,12 @@ fn worker_thread(thread_index: u32, cpu_count: *u32) void {
thread.llvm = .{ thread.llvm = .{
.context = context, .context = context,
.module = module, .module = module,
.builder = builder,
.attributes = attributes, .attributes = attributes,
.target_machine = target_machine, .target_machine = target_machine,
}; };
const debug_info = false; const debug_info = false;
for (thread.external_functions.slice()) |*nat_function| { for (thread.external_functions.slice()) |*nat_function| {
_ = llvm_get_function(thread, nat_function, true); _ = llvm_get_function(thread, nat_function, true);
} }
@ -2919,8 +3035,26 @@ fn worker_thread(thread_index: u32, cpu_count: *u32) void {
_ = llvm_get_function(thread, &nat_function.declaration, false); _ = llvm_get_function(thread, &nat_function.declaration, false);
} }
for (thread.global_variables.slice()) |*nat_global| {
const global_type = llvm_get_type(thread, nat_global.type);
const linkage: LLVM.Linkage = switch (nat_global.global_symbol.attributes.@"export") {
true => .@"extern",
false => .internal,
};
const constant = switch (nat_global.global_symbol.attributes.mutability) {
.@"var" => false,
.@"const" => true,
};
const initializer = llvm_get_value(thread, nat_global.initial_value).toConstant() orelse unreachable;
const thread_local_mode = LLVM.ThreadLocalMode.not_thread_local;
const externally_initialized = false;
const name = thread.identifiers.get(nat_global.global_symbol.global_declaration.declaration.name).?;
const global_variable = module.addGlobalVariable(global_type, constant, linkage, initializer, name.ptr, name.len, null, thread_local_mode, address_space, externally_initialized);
nat_global.global_symbol.value.llvm = global_variable.toValue();
}
for (thread.functions.slice()) |*nat_function| { for (thread.functions.slice()) |*nat_function| {
const function = nat_function.declaration.value.llvm.?.toFunction() orelse unreachable; const function = nat_function.declaration.global_symbol.value.llvm.?.toFunction() orelse unreachable;
var basic_block_command_buffer = BasicBlock.CommandList{}; var basic_block_command_buffer = BasicBlock.CommandList{};
var emit_allocas = true; var emit_allocas = true;
{ {
@ -2940,21 +3074,21 @@ fn worker_thread(thread_index: u32, cpu_count: *u32) void {
const basic_block_node = basic_block_command_buffer.first orelse unreachable; const basic_block_node = basic_block_command_buffer.first orelse unreachable;
const basic_block: *BasicBlock = @fieldParentPtr("command_node", basic_block_node); const basic_block: *BasicBlock = @fieldParentPtr("command_node", basic_block_node);
const llvm_basic_block = basic_block.get_llvm(); const llvm_basic_block = basic_block.get_llvm();
thread.llvm.builder.setInsertPoint(llvm_basic_block); builder.setInsertPoint(llvm_basic_block);
var last_block = basic_block_node; var last_block = basic_block_node;
if (emit_allocas) { if (emit_allocas) {
for (nat_function.arguments.slice(), 0..) |argument, argument_index| { for (nat_function.arguments.slice(), 0..) |argument, argument_index| {
const alloca_type = llvm_get_type(thread, argument.type); const alloca_type = llvm_get_type(thread, argument.type);
argument.instruction.value.llvm = thread.llvm.builder.createAlloca(alloca_type, address_space, null, "", "".len, argument.alignment).toValue(); argument.instruction.value.llvm = builder.createAlloca(alloca_type, address_space, null, "", "".len, argument.alignment).toValue();
const llvm_argument = function.getArgument(@intCast(argument_index)); const llvm_argument = function.getArgument(@intCast(argument_index));
argument.value.llvm = llvm_argument.toValue(); argument.value.llvm = llvm_argument.toValue();
} }
for (nat_function.stack_slots.slice()) |local_slot| { for (nat_function.stack_slots.slice()) |local_slot| {
const alloca_type = llvm_get_type(thread, local_slot.type); const alloca_type = llvm_get_type(thread, local_slot.type);
local_slot.instruction.value.llvm = thread.llvm.builder.createAlloca(alloca_type, address_space, null, "", "".len, local_slot.alignment).toValue(); local_slot.instruction.value.llvm = builder.createAlloca(alloca_type, address_space, null, "", "".len, local_slot.alignment).toValue();
} }
emit_allocas = false; emit_allocas = false;
@ -2980,7 +3114,7 @@ fn worker_thread(thread_index: u32, cpu_count: *u32) void {
.ret => block: { .ret => block: {
const return_instruction = instruction.get_payload(.ret); const return_instruction = instruction.get_payload(.ret);
const return_value = llvm_get_value(thread, return_instruction.value); const return_value = llvm_get_value(thread, return_instruction.value);
const ret = thread.llvm.builder.createRet(return_value); const ret = builder.createRet(return_value);
break :block ret.toValue(); break :block ret.toValue();
}, },
.integer_binary_operation => block: { .integer_binary_operation => block: {
@ -3020,7 +3154,7 @@ fn worker_thread(thread_index: u32, cpu_count: *u32) void {
const args = arguments.constSlice(); const args = arguments.constSlice();
const call_i = thread.llvm.builder.createCall(function_type, callee, args.ptr, args.len, "", "".len, null); const call_i = builder.createCall(function_type, callee, args.ptr, args.len, "", "".len, null);
break :block call_i.toValue(); break :block call_i.toValue();
}, },
.integer_compare => block: { .integer_compare => block: {
@ -3293,7 +3427,7 @@ fn llvm_get_file(thread: *Thread, file_index: u32) *LLVMFile {
} }
fn llvm_get_function(thread: *Thread, nat_function: *Function.Declaration, override_extern: bool) *LLVM.Value.Constant.Function { fn llvm_get_function(thread: *Thread, nat_function: *Function.Declaration, override_extern: bool) *LLVM.Value.Constant.Function {
if (nat_function.value.llvm) |llvm| return llvm.toFunction() orelse unreachable else { if (nat_function.global_symbol.value.llvm) |llvm| return llvm.toFunction() orelse unreachable else {
_ = override_extern; // autofix _ = override_extern; // autofix
const function_name = thread.identifiers.get(nat_function.global_symbol.global_declaration.declaration.name) orelse unreachable; const function_name = thread.identifiers.get(nat_function.global_symbol.global_declaration.declaration.name) orelse unreachable;
const return_type = llvm_get_type(thread, nat_function.return_type); const return_type = llvm_get_type(thread, nat_function.return_type);
@ -3377,7 +3511,7 @@ fn llvm_get_function(thread: *Thread, nat_function: *Function.Declaration, overr
function.setSubprogram(subprogram); function.setSubprogram(subprogram);
} }
nat_function.value.llvm = function.toValue(); nat_function.global_symbol.value.llvm = function.toValue();
return function; return function;
} }
} }
@ -3753,7 +3887,17 @@ pub fn analyze_local_block(thread: *Thread, analyzer: *Analyzer, parser: *Parser
const local_symbol = local_declaration.to_symbol(); const local_symbol = local_declaration.to_symbol();
break :block local_symbol.type; break :block local_symbol.type;
}, },
.global => block: {
const global_declaration = declaration_lookup.declaration.*.get_payload(.global);
const global_symbol = global_declaration.to_symbol();
switch (global_symbol.id) {
.global_variable => {
const global_variable = global_symbol.get_payload(.global_variable);
break :block global_variable.type;
},
else => |t| @panic(@tagName(t)), else => |t| @panic(@tagName(t)),
}
},
}; };
const right = parser.parse_expression(analyzer, thread, file, declaration_type, .right); const right = parser.parse_expression(analyzer, thread, file, declaration_type, .right);
const source = switch (is_binary_operation) { const source = switch (is_binary_operation) {
@ -3838,7 +3982,18 @@ fn get_declaration_value(analyzer: *Analyzer, thread: *Thread, declaration: *Dec
declaration_type = argument_symbol.type; declaration_type = argument_symbol.type;
break :block &argument_symbol.instruction.value; break :block &argument_symbol.instruction.value;
}, },
.global => block: {
const global_declaration = declaration.get_payload(.global);
const global_symbol = global_declaration.to_symbol();
switch (global_symbol.id) {
.global_variable => {
const global_variable = global_symbol.get_payload(.global_variable);
declaration_type = global_variable.type;
},
else => |t| @panic(@tagName(t)), else => |t| @panic(@tagName(t)),
}
break :block &global_symbol.value;
},
}; };
if (maybe_type) |ty| { if (maybe_type) |ty| {
@ -3900,11 +4055,55 @@ pub fn analyze_file(thread: *Thread, file_index: u32) void {
parser.skip_space(src); parser.skip_space(src);
const symbol_identifier_start = parser.i; const symbol_identifier_start = parser.i;
_ = symbol_identifier_start; // autofix _ = symbol_identifier_start; // autofix
const identifier = parser.parse_identifier(thread, src); const global_name = parser.parse_identifier(thread, src);
_ = identifier; // autofix
if (file.scope.scope.get_global_declaration(global_name)) |existing_global| {
_ = existing_global; // autofix
exit(1); exit(1);
}
parser.skip_space(src);
parser.expect_character(src, ':');
parser.skip_space(src);
const global_type = parser.parse_type_expression(thread, src);
parser.skip_space(src);
parser.expect_character(src, '=');
parser.skip_space(src);
const global_initial_value = parser.parse_constant_expression(thread, file, global_type);
parser.expect_character(src, ';');
const global_variable = thread.global_variables.append(.{
.global_symbol = .{
.global_declaration = .{
.declaration = .{
.id = .global,
.name = global_name,
},
.id = .global_symbol,
},
.value = .{
.sema = .{
.thread = thread.get_index(),
.resolved = global_type.sema.resolved and global_initial_value.sema.resolved,
.id = .global_symbol,
},
},
.alignment = global_type.alignment,
.id = .global_variable,
},
.type = global_type,
.initial_value = global_initial_value,
});
file.scope.scope.declarations.put_no_clobber(global_name, &global_variable.global_symbol.global_declaration.declaration);
}, },
'f' => { 'f' => {
if (src[parser.i + 1] == 'n') { if (src[parser.i + 1] == 'n') {
@ -3922,17 +4121,19 @@ pub fn analyze_file(thread: *Thread, file_index: u32) void {
.name = std.math.maxInt(u32), .name = std.math.maxInt(u32),
.id = .global, .id = .global,
}, },
.id = .function_definition, .id = .global_symbol,
}, },
}, .alignment = 1,
.file = file_index,
.value = .{ .value = .{
.sema = .{ .sema = .{
.thread = thread.get_index(), .thread = thread.get_index(),
.resolved = true, // TODO: is this correct? .resolved = true, // TODO: is this correct?
.id = .function_declaration, .id = .global_symbol,
}, },
}, },
.id = .function_definition,
},
.file = file_index,
}, },
.scope = .{ .scope = .{
.scope = .{ .scope = .{

View File

@ -0,0 +1,5 @@
>n: s32 = 1;
fn[cc(.c)] main[export]() s32 {
n = 0;
return n;
}