wip
This commit is contained in:
parent
9b1c5ce7b0
commit
dfb18ceba2
515
src/compiler.bbb
515
src/compiler.bbb
@ -97,6 +97,19 @@ align_forward = fn (value: u64, alignment: u64) u64
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
next_power_of_two = fn (n: u64) u64
|
||||||
|
{
|
||||||
|
n -= 1;
|
||||||
|
n |= n >> 1;
|
||||||
|
n |= n >> 2;
|
||||||
|
n |= n >> 4;
|
||||||
|
n |= n >> 8;
|
||||||
|
n |= n >> 16;
|
||||||
|
n |= n >> 32;
|
||||||
|
n += 1;
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
string_no_match = #integer_max(u64);
|
string_no_match = #integer_max(u64);
|
||||||
|
|
||||||
c_string_length = fn (c_string: &u8) u64
|
c_string_length = fn (c_string: &u8) u64
|
||||||
@ -551,10 +564,14 @@ LLVMTargetMachineOptions = opaque;
|
|||||||
|
|
||||||
LLVMIntrinsicIndex = enum u32
|
LLVMIntrinsicIndex = enum u32
|
||||||
{
|
{
|
||||||
trap,
|
"llvm.smax",
|
||||||
va_start,
|
"llvm.smin",
|
||||||
va_end,
|
"llvm.trap",
|
||||||
va_copy,
|
"llvm.umax",
|
||||||
|
"llvm.umin",
|
||||||
|
"llvm.va_start",
|
||||||
|
"llvm.va_end",
|
||||||
|
"llvm.va_copy",
|
||||||
}
|
}
|
||||||
|
|
||||||
CompilerCommand = enum
|
CompilerCommand = enum
|
||||||
@ -658,6 +675,7 @@ Type = struct;
|
|||||||
Value = struct;
|
Value = struct;
|
||||||
Local = struct;
|
Local = struct;
|
||||||
Block = struct;
|
Block = struct;
|
||||||
|
Module = struct;
|
||||||
|
|
||||||
ScopeKind = enum
|
ScopeKind = enum
|
||||||
{
|
{
|
||||||
@ -746,7 +764,7 @@ TypeId = enum
|
|||||||
|
|
||||||
TypeInteger = struct
|
TypeInteger = struct
|
||||||
{
|
{
|
||||||
bit_count: u32,
|
bit_count: u64,
|
||||||
signed: u1,
|
signed: u1,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -767,9 +785,67 @@ AbiRegisterCount = union
|
|||||||
system_v: AbiRegisterCountSystemV,
|
system_v: AbiRegisterCountSystemV,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
AbiInformationPadding = union
|
||||||
|
{
|
||||||
|
type: &Type,
|
||||||
|
unpadded_coerce_and_expand_type: &Type,
|
||||||
|
}
|
||||||
|
|
||||||
|
AbiInformationDirectAttributes = struct
|
||||||
|
{
|
||||||
|
offset: u32,
|
||||||
|
alignment: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
AbiInformationIndirectAttributes = struct
|
||||||
|
{
|
||||||
|
alignment: u32,
|
||||||
|
address_space: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
AbiInformationAttributes = union
|
||||||
|
{
|
||||||
|
direct: AbiInformationDirectAttributes,
|
||||||
|
indirect: AbiInformationIndirectAttributes,
|
||||||
|
alloca_field_index: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
AbiKind = enum
|
||||||
|
{
|
||||||
|
ignore,
|
||||||
|
direct,
|
||||||
|
extend,
|
||||||
|
indirect,
|
||||||
|
indirect_aliased,
|
||||||
|
expand,
|
||||||
|
coerce_and_expand,
|
||||||
|
in_alloca,
|
||||||
|
}
|
||||||
|
|
||||||
|
AbiFlags = struct
|
||||||
|
{
|
||||||
|
kind: AbiKind,
|
||||||
|
padding_in_reg: u1,
|
||||||
|
in_alloca_sret: u1,
|
||||||
|
in_alloca_indirect: u1,
|
||||||
|
in_alloca_by_value: u1,
|
||||||
|
in_alloca_realign: u1,
|
||||||
|
sret_after_this: u1,
|
||||||
|
in_reg: u1,
|
||||||
|
can_be_flattened: u1,
|
||||||
|
sign_extension: u1,
|
||||||
|
}
|
||||||
|
|
||||||
AbiInformation = struct
|
AbiInformation = struct
|
||||||
{
|
{
|
||||||
foo: u32,
|
semantic_type: &Type,
|
||||||
|
coerce_to_type: &Type,
|
||||||
|
padding: AbiInformationPadding,
|
||||||
|
padding_argument_index: u16,
|
||||||
|
attributes: AbiInformationAttributes,
|
||||||
|
flags: AbiFlags,
|
||||||
|
abi_start: u16,
|
||||||
|
abi_count: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeFunction = struct
|
TypeFunction = struct
|
||||||
@ -802,12 +878,88 @@ Type = struct
|
|||||||
scope: &Scope,
|
scope: &Scope,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
align_bit_count = fn (bit_count: u64) u64
|
||||||
|
{
|
||||||
|
>aligned_bit_count = #max(next_power_of_two(bit_count), 8);
|
||||||
|
assert(aligned_bit_count % 8 == 0);
|
||||||
|
return aligned_bit_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
aligned_byte_count_from_bit_count = fn (bit_count: u64) u64
|
||||||
|
{
|
||||||
|
>aligned_bit_count = align_bit_count(bit_count);
|
||||||
|
return aligned_bit_count / 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
get_byte_size = fn (type: &Type) u64
|
||||||
|
{
|
||||||
|
switch (type.id)
|
||||||
|
{
|
||||||
|
.integer =>
|
||||||
|
{
|
||||||
|
>byte_count = aligned_byte_count_from_bit_count(type.content.integer.bit_count);
|
||||||
|
assert(byte_count == 1 or byte_count == 2 or byte_count == 4 or byte_count == 8 or byte_count == 16);
|
||||||
|
return byte_count;
|
||||||
|
},
|
||||||
|
else =>
|
||||||
|
{
|
||||||
|
#trap();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is_integral_or_enumeration_type = fn (type: &Type) u1
|
||||||
|
{
|
||||||
|
switch (type.id)
|
||||||
|
{
|
||||||
|
.integer, .bits, .enum =>
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
},
|
||||||
|
.array, .struct =>
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
else =>
|
||||||
|
{
|
||||||
|
unreachable;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is_promotable_integer_type_for_abi = fn (type: &Type) u1
|
||||||
|
{
|
||||||
|
switch (type.id)
|
||||||
|
{
|
||||||
|
.integer =>
|
||||||
|
{
|
||||||
|
return type.content.integer.bit_count < 32;
|
||||||
|
},
|
||||||
|
else =>
|
||||||
|
{
|
||||||
|
#trap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve_type_in_place_abi = fn (module: &Module, type: &Type) void;
|
||||||
|
resolve_type_in_place_memory = fn (module: &Module, type: &Type) void;
|
||||||
|
resolve_type_in_place_debug = fn (module: &Module, type: &Type) void;
|
||||||
|
|
||||||
|
resolve_type_in_place = fn (module: &Module, type: &Type) void
|
||||||
|
{
|
||||||
|
resolve_type_in_place_abi(module, type);
|
||||||
|
resolve_type_in_place_memory(module, type);
|
||||||
|
resolve_type_in_place_debug(module, type);
|
||||||
|
}
|
||||||
|
|
||||||
ValueId = enum
|
ValueId = enum
|
||||||
{
|
{
|
||||||
infer_or_ignore,
|
infer_or_ignore,
|
||||||
external_function,
|
external_function,
|
||||||
function,
|
function,
|
||||||
constant_integer,
|
constant_integer,
|
||||||
|
global,
|
||||||
}
|
}
|
||||||
|
|
||||||
ValueConstantInteger = struct
|
ValueConstantInteger = struct
|
||||||
@ -1223,7 +1375,7 @@ integer_type = fn (module: &Module, integer: TypeInteger) &Type
|
|||||||
{
|
{
|
||||||
assert(integer.bit_count != 0);
|
assert(integer.bit_count != 0);
|
||||||
assert(integer.bit_count <= 64);
|
assert(integer.bit_count <= 64);
|
||||||
>index = #select(integer.bit_count == 128, i128_offset + #extend(integer.signed), #extend(integer.bit_count) - 1 + 64 * #extend(integer.signed));
|
>index = #select(integer.bit_count == 128, i128_offset + #extend(integer.signed), integer.bit_count - 1 + 64 * #extend(integer.signed));
|
||||||
>result = module.scope.types.first + index;
|
>result = module.scope.types.first + index;
|
||||||
assert(result.id == .integer);
|
assert(result.id == .integer);
|
||||||
assert(result.content.integer.bit_count == integer.bit_count);
|
assert(result.content.integer.bit_count == integer.bit_count);
|
||||||
@ -1685,7 +1837,7 @@ parse_type = fn (module: &Module, scope: &Scope) &Type
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
>result = integer_type(module, { .bit_count = #truncate(bit_count), .signed = is_signed });
|
>result = integer_type(module, { .bit_count = bit_count, .signed = is_signed });
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -2784,6 +2936,278 @@ parse = fn (module: &Module) void
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resolve_alias = fn (module: &Module, type: &Type) &Type
|
||||||
|
{
|
||||||
|
>result: &Type = zero;
|
||||||
|
|
||||||
|
switch (type.id)
|
||||||
|
{
|
||||||
|
.void,
|
||||||
|
.integer,
|
||||||
|
=>
|
||||||
|
{
|
||||||
|
result = type;
|
||||||
|
},
|
||||||
|
else =>
|
||||||
|
{
|
||||||
|
#trap();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(result != zero);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve_type_in_place_abi = fn (module: &Module, type: &Type) void
|
||||||
|
{
|
||||||
|
#trap();
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve_type_in_place_memory = fn (module: &Module, type: &Type) void
|
||||||
|
{
|
||||||
|
#trap();
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve_type_in_place_debug = fn (module: &Module, type: &Type) void
|
||||||
|
{
|
||||||
|
#trap();
|
||||||
|
}
|
||||||
|
|
||||||
|
AbiSystemVClass = enum
|
||||||
|
{
|
||||||
|
none,
|
||||||
|
integer,
|
||||||
|
sse,
|
||||||
|
sse_up,
|
||||||
|
x87,
|
||||||
|
x87_up,
|
||||||
|
complex_x87,
|
||||||
|
memory,
|
||||||
|
}
|
||||||
|
|
||||||
|
AbiSystemVClassifyArgument = struct
|
||||||
|
{
|
||||||
|
base_offset: u64,
|
||||||
|
is_variable_argument: u1,
|
||||||
|
is_register_call: u1,
|
||||||
|
}
|
||||||
|
|
||||||
|
abi_system_v_classify_type = fn (type: &Type, options: AbiSystemVClassifyArgument) [2]AbiSystemVClass
|
||||||
|
{
|
||||||
|
>result: [2]AbiSystemVClass = zero;
|
||||||
|
|
||||||
|
>is_memory = options.base_offset >= 8;
|
||||||
|
>current_index: u64 = #extend(is_memory);
|
||||||
|
>not_current_index: u64 = #extend(!is_memory);
|
||||||
|
assert(current_index != not_current_index);
|
||||||
|
result[current_index] = .memory;
|
||||||
|
|
||||||
|
switch (type.id)
|
||||||
|
{
|
||||||
|
.void, .noreturn =>
|
||||||
|
{
|
||||||
|
result[current_index] = .none;
|
||||||
|
},
|
||||||
|
.integer =>
|
||||||
|
{
|
||||||
|
>bit_count = type.content.integer.bit_count;
|
||||||
|
if (bit_count <= 64)
|
||||||
|
{
|
||||||
|
result[current_index] = .integer;
|
||||||
|
}
|
||||||
|
else if (bit_count == 128)
|
||||||
|
{
|
||||||
|
#trap();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
report_error();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
else =>
|
||||||
|
{
|
||||||
|
#trap();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
contains_no_user_data = fn (type: &Type, start: u64, end: u64) u1
|
||||||
|
{
|
||||||
|
>byte_size = get_byte_size(type);
|
||||||
|
>result = byte_size <= start;
|
||||||
|
|
||||||
|
if (!result)
|
||||||
|
{
|
||||||
|
switch (type.id)
|
||||||
|
{
|
||||||
|
.struct =>
|
||||||
|
{
|
||||||
|
result = 1;
|
||||||
|
#trap();
|
||||||
|
},
|
||||||
|
.array, .enum_array =>
|
||||||
|
{
|
||||||
|
result = 1;
|
||||||
|
#trap();
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
abi_system_v_get_integer_type_at_offset = fn (module: &Module, type: &Type, offset: u64, source_type: &Type, source_offset: u64) &Type
|
||||||
|
{
|
||||||
|
switch (type.id)
|
||||||
|
{
|
||||||
|
.integer =>
|
||||||
|
{
|
||||||
|
if (offset == 0)
|
||||||
|
{
|
||||||
|
>bit_count = type.content.integer.bit_count;
|
||||||
|
>start = source_offset + get_byte_size(type);
|
||||||
|
>end = source_offset + 8;
|
||||||
|
|
||||||
|
if (bit_count == 64)
|
||||||
|
{
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bit_count == 32 or bit_count == 16 or bit_count == 8)
|
||||||
|
{
|
||||||
|
if (contains_no_user_data(source_type, start, end))
|
||||||
|
{
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
>source_size = get_byte_size(source_type);
|
||||||
|
assert(source_size != source_offset);
|
||||||
|
>byte_count = source_size - source_offset;
|
||||||
|
>bit_count = #select(byte_count > 8, 64, byte_count * 8);
|
||||||
|
>result = integer_type(module, { .bit_count = bit_count, .signed = 0 });
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
AbiSystemVDirect = struct
|
||||||
|
{
|
||||||
|
semantic_type: &Type,
|
||||||
|
type: &Type,
|
||||||
|
padding: &Type,
|
||||||
|
offset: u32,
|
||||||
|
alignment: u32,
|
||||||
|
cannot_be_flattened: u1,
|
||||||
|
}
|
||||||
|
|
||||||
|
abi_system_v_get_direct = fn (module: &Module, direct: AbiSystemVDirect) AbiInformation
|
||||||
|
{
|
||||||
|
>result: AbiInformation = {
|
||||||
|
.semantic_type = direct.semantic_type,
|
||||||
|
.flags = {
|
||||||
|
.kind = .direct,
|
||||||
|
zero,
|
||||||
|
},
|
||||||
|
zero,
|
||||||
|
};
|
||||||
|
|
||||||
|
resolve_type_in_place(module, direct.semantic_type);
|
||||||
|
resolve_type_in_place(module, direct.type);
|
||||||
|
|
||||||
|
if (direct.padding)
|
||||||
|
{
|
||||||
|
resolve_type_in_place(module, direct.padding);
|
||||||
|
}
|
||||||
|
|
||||||
|
#trap();
|
||||||
|
}
|
||||||
|
|
||||||
|
abi_system_v_classify_return_type = fn (module: &Module, semantic_return_type: &Type) AbiInformation
|
||||||
|
{
|
||||||
|
>classes = abi_system_v_classify_type(semantic_return_type, zero);
|
||||||
|
assert(classes[1] != .memory or classes[0] == .memory);
|
||||||
|
assert(classes[1] != .sse_up or classes[0] == .sse);
|
||||||
|
|
||||||
|
>low_type: &Type = zero;
|
||||||
|
|
||||||
|
switch (classes[0])
|
||||||
|
{
|
||||||
|
.none =>
|
||||||
|
{
|
||||||
|
if (classes[1] == .none)
|
||||||
|
{
|
||||||
|
#trap();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
report_error();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.integer =>
|
||||||
|
{
|
||||||
|
low_type = abi_system_v_get_integer_type_at_offset(module, semantic_return_type, 0, semantic_return_type, 0);
|
||||||
|
|
||||||
|
if (classes[1] == .none and low_type.id == .integer)
|
||||||
|
{
|
||||||
|
if (is_integral_or_enumeration_type(semantic_return_type) and? is_promotable_integer_type_for_abi(semantic_return_type))
|
||||||
|
{
|
||||||
|
#trap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
else =>
|
||||||
|
{
|
||||||
|
#trap();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
>high_type: &Type = zero;
|
||||||
|
|
||||||
|
switch (classes[1])
|
||||||
|
{
|
||||||
|
.none => {},
|
||||||
|
.integer =>
|
||||||
|
{
|
||||||
|
>high_offset: u64 = 8;
|
||||||
|
high_type = abi_system_v_get_integer_type_at_offset(module, semantic_return_type, high_offset, semantic_return_type, high_offset);
|
||||||
|
|
||||||
|
if (classes[0] == .none)
|
||||||
|
{
|
||||||
|
#trap();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
else =>
|
||||||
|
{
|
||||||
|
#trap();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if (high_type)
|
||||||
|
{
|
||||||
|
#trap();
|
||||||
|
}
|
||||||
|
|
||||||
|
>result = abi_system_v_get_direct(module, {
|
||||||
|
.semantic_type = semantic_return_type,
|
||||||
|
.type = low_type,
|
||||||
|
zero,
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResolvedCallingConvention = enum
|
||||||
|
{
|
||||||
|
system_v,
|
||||||
|
win64,
|
||||||
|
}
|
||||||
|
|
||||||
emit = fn (module: &Module) void
|
emit = fn (module: &Module) void
|
||||||
{
|
{
|
||||||
assert(!module.current_function);
|
assert(!module.current_function);
|
||||||
@ -2889,6 +3313,79 @@ emit = fn (module: &Module) void
|
|||||||
>name = #enum_name(e);
|
>name = #enum_name(e);
|
||||||
module.llvm.intrinsic_table[i] = LLVMLookupIntrinsicID(name.pointer, name.length);
|
module.llvm.intrinsic_table[i] = LLVMLookupIntrinsicID(name.pointer, name.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
>global = module.first_global;
|
||||||
|
|
||||||
|
while (global)
|
||||||
|
{
|
||||||
|
assert(!module.current_function);
|
||||||
|
assert(!module.current_macro_instantiation);
|
||||||
|
assert(!module.current_macro_declaration);
|
||||||
|
|
||||||
|
if (global.emitted)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
>global_pointer_type = global.variable.storage.type;
|
||||||
|
assert(global_pointer_type.id == .pointer);
|
||||||
|
>global_value_type = global_pointer_type.content.pointer.element_type;
|
||||||
|
|
||||||
|
switch (global.variable.storage.id)
|
||||||
|
{
|
||||||
|
.function, .external_function =>
|
||||||
|
{
|
||||||
|
>function_type = &global_value_type.content.function;
|
||||||
|
>semantic_argument_types = function_type.semantic_argument_types;
|
||||||
|
>semantic_return_type = function_type.semantic_return_type;
|
||||||
|
function_type.argument_abis = arena_allocate_slice[AbiInformation](module.arena, semantic_argument_types.length);
|
||||||
|
>llvm_abi_argument_type_buffer: [64]&LLVMType = undefined;
|
||||||
|
|
||||||
|
>resolved_calling_convention: ResolvedCallingConvention = undefined;
|
||||||
|
switch (function_type.calling_convention)
|
||||||
|
{
|
||||||
|
.c =>
|
||||||
|
{
|
||||||
|
// TODO: switch on platform
|
||||||
|
resolved_calling_convention = .system_v;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
>is_reg_call = resolved_calling_convention == .system_v and 0; // TODO: regcall calling convention
|
||||||
|
|
||||||
|
switch (resolved_calling_convention)
|
||||||
|
{
|
||||||
|
.system_v =>
|
||||||
|
{
|
||||||
|
function_type.available_registers = {
|
||||||
|
.system_v = {
|
||||||
|
.gpr = #select(is_reg_call, 11, 6),
|
||||||
|
.sse = #select(is_reg_call, 16, 8),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
function_type.return_abi = abi_system_v_classify_return_type(module, resolve_alias(module, semantic_return_type));
|
||||||
|
#trap();
|
||||||
|
},
|
||||||
|
.win64 =>
|
||||||
|
{
|
||||||
|
#trap();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.global =>
|
||||||
|
{
|
||||||
|
#trap();
|
||||||
|
},
|
||||||
|
else =>
|
||||||
|
{
|
||||||
|
report_error();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
global = global.next;
|
||||||
|
}
|
||||||
|
|
||||||
|
#trap();
|
||||||
}
|
}
|
||||||
|
|
||||||
compile = fn (arena: &Arena, options: CompileOptions) void
|
compile = fn (arena: &Arena, options: CompileOptions) void
|
||||||
@ -2919,7 +3416,7 @@ compile = fn (arena: &Arena, options: CompileOptions) void
|
|||||||
type_it.& = {
|
type_it.& = {
|
||||||
.content = {
|
.content = {
|
||||||
.integer = {
|
.integer = {
|
||||||
.bit_count = #truncate(bit_count),
|
.bit_count = bit_count,
|
||||||
.signed = sign,
|
.signed = sign,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -435,6 +435,10 @@ global_variable String names[] =
|
|||||||
string_literal("opaque"),
|
string_literal("opaque"),
|
||||||
string_literal("basic_struct_passing"),
|
string_literal("basic_struct_passing"),
|
||||||
string_literal("enum_arbitrary_abi"),
|
string_literal("enum_arbitrary_abi"),
|
||||||
|
string_literal("enum_debug_info"),
|
||||||
|
string_literal("return_array"),
|
||||||
|
string_literal("bool_pair"),
|
||||||
|
string_literal("min_max"),
|
||||||
};
|
};
|
||||||
|
|
||||||
void entry_point(Slice<char* const> arguments, Slice<char* const> envp)
|
void entry_point(Slice<char* const> arguments, Slice<char* const> envp)
|
||||||
|
@ -351,13 +351,16 @@ union AbiRegisterCount
|
|||||||
AbiRegisterCountSystemV system_v;
|
AbiRegisterCountSystemV system_v;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TypeFunction
|
struct TypeFunctionBase
|
||||||
{
|
{
|
||||||
Type* semantic_return_type;
|
Type* semantic_return_type;
|
||||||
Slice<Type*> semantic_argument_types;
|
Slice<Type*> semantic_argument_types;
|
||||||
CallingConvention calling_convention;
|
CallingConvention calling_convention;
|
||||||
bool is_variable_arguments;
|
bool is_variable_arguments;
|
||||||
// ABI
|
};
|
||||||
|
|
||||||
|
struct TypeFunctionAbi
|
||||||
|
{
|
||||||
Slice<Type*> abi_argument_types;
|
Slice<Type*> abi_argument_types;
|
||||||
Type* abi_return_type;
|
Type* abi_return_type;
|
||||||
AbiRegisterCount available_registers;
|
AbiRegisterCount available_registers;
|
||||||
@ -365,6 +368,12 @@ struct TypeFunction
|
|||||||
AbiInformation return_abi;
|
AbiInformation return_abi;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct TypeFunction
|
||||||
|
{
|
||||||
|
TypeFunctionBase base;
|
||||||
|
TypeFunctionAbi abi;
|
||||||
|
};
|
||||||
|
|
||||||
struct TypePointer
|
struct TypePointer
|
||||||
{
|
{
|
||||||
Type* element_type;
|
Type* element_type;
|
||||||
@ -629,6 +638,11 @@ fn u64 get_bit_size(Type* type)
|
|||||||
case TypeId::integer: return type->integer.bit_count;
|
case TypeId::integer: return type->integer.bit_count;
|
||||||
case TypeId::enumerator: return get_bit_size(type->enumerator.backing_type);
|
case TypeId::enumerator: return get_bit_size(type->enumerator.backing_type);
|
||||||
case TypeId::alias: return get_bit_size(type->alias.type);
|
case TypeId::alias: return get_bit_size(type->alias.type);
|
||||||
|
case TypeId::array: return get_byte_size(type->array.element_type) * type->array.element_count * 8;
|
||||||
|
case TypeId::pointer: return 64;
|
||||||
|
case TypeId::structure: return type->structure.byte_size * 8;
|
||||||
|
case TypeId::union_type: return type->union_type.byte_size * 8;
|
||||||
|
case TypeId::enum_array: return get_byte_size(type->enum_array.element_type) * type->enum_array.enum_type->enumerator.fields.length * 8;
|
||||||
default: trap();
|
default: trap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -793,7 +807,7 @@ struct Block
|
|||||||
enum class ValueId
|
enum class ValueId
|
||||||
{
|
{
|
||||||
infer_or_ignore,
|
infer_or_ignore,
|
||||||
external_function,
|
forward_declared_function,
|
||||||
function,
|
function,
|
||||||
constant_integer,
|
constant_integer,
|
||||||
unary,
|
unary,
|
||||||
@ -905,6 +919,8 @@ enum class BinaryId
|
|||||||
logical_or,
|
logical_or,
|
||||||
logical_and_shortcircuit,
|
logical_and_shortcircuit,
|
||||||
logical_or_shortcircuit,
|
logical_or_shortcircuit,
|
||||||
|
max,
|
||||||
|
min,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ValueBinary
|
struct ValueBinary
|
||||||
@ -1078,6 +1094,7 @@ struct Value
|
|||||||
case ValueId::array_expression:
|
case ValueId::array_expression:
|
||||||
case ValueId::call:
|
case ValueId::call:
|
||||||
case ValueId::select:
|
case ValueId::select:
|
||||||
|
case ValueId::slice_expression:
|
||||||
return false;
|
return false;
|
||||||
case ValueId::variable_reference:
|
case ValueId::variable_reference:
|
||||||
{
|
{
|
||||||
@ -1164,7 +1181,11 @@ struct LLVMIntrinsicId
|
|||||||
|
|
||||||
enum class IntrinsicIndex
|
enum class IntrinsicIndex
|
||||||
{
|
{
|
||||||
|
smax,
|
||||||
|
smin,
|
||||||
trap,
|
trap,
|
||||||
|
umax,
|
||||||
|
umin,
|
||||||
va_start,
|
va_start,
|
||||||
va_end,
|
va_end,
|
||||||
va_copy,
|
va_copy,
|
||||||
@ -1172,7 +1193,11 @@ enum class IntrinsicIndex
|
|||||||
};
|
};
|
||||||
|
|
||||||
global_variable String intrinsic_names[] = {
|
global_variable String intrinsic_names[] = {
|
||||||
|
string_literal("llvm.smax"),
|
||||||
|
string_literal("llvm.smin"),
|
||||||
string_literal("llvm.trap"),
|
string_literal("llvm.trap"),
|
||||||
|
string_literal("llvm.umax"),
|
||||||
|
string_literal("llvm.umin"),
|
||||||
string_literal("llvm.va_start"),
|
string_literal("llvm.va_start"),
|
||||||
string_literal("llvm.va_end"),
|
string_literal("llvm.va_end"),
|
||||||
string_literal("llvm.va_copy"),
|
string_literal("llvm.va_copy"),
|
||||||
@ -1213,6 +1238,7 @@ struct Module
|
|||||||
Type* first_pair_struct_type;
|
Type* first_pair_struct_type;
|
||||||
Type* first_array_type;
|
Type* first_array_type;
|
||||||
Type* first_enum_array_type;
|
Type* first_enum_array_type;
|
||||||
|
Type* first_function_type;
|
||||||
|
|
||||||
Type* va_list_type;
|
Type* va_list_type;
|
||||||
|
|
||||||
|
689
src/emitter.cpp
689
src/emitter.cpp
File diff suppressed because it is too large
Load Diff
198
src/parser.cpp
198
src/parser.cpp
@ -10,6 +10,8 @@ enum class ValueIntrinsic
|
|||||||
integer_max,
|
integer_max,
|
||||||
int_from_enum,
|
int_from_enum,
|
||||||
int_from_pointer,
|
int_from_pointer,
|
||||||
|
max,
|
||||||
|
min,
|
||||||
pointer_cast,
|
pointer_cast,
|
||||||
pointer_from_int,
|
pointer_from_int,
|
||||||
select,
|
select,
|
||||||
@ -668,7 +670,7 @@ fn Type* parse_type(Module* module, Scope* scope)
|
|||||||
{
|
{
|
||||||
case TypeIntrinsic::return_type:
|
case TypeIntrinsic::return_type:
|
||||||
{
|
{
|
||||||
auto return_type = module->current_function->variable.type->function.semantic_return_type;
|
auto return_type = module->current_function->variable.type->function.base.semantic_return_type;
|
||||||
return return_type;
|
return return_type;
|
||||||
} break;
|
} break;
|
||||||
case TypeIntrinsic::count: report_error();
|
case TypeIntrinsic::count: report_error();
|
||||||
@ -862,6 +864,8 @@ fn Token tokenize(Module* module)
|
|||||||
string_literal("integer_max"),
|
string_literal("integer_max"),
|
||||||
string_literal("int_from_enum"),
|
string_literal("int_from_enum"),
|
||||||
string_literal("int_from_pointer"),
|
string_literal("int_from_pointer"),
|
||||||
|
string_literal("max"),
|
||||||
|
string_literal("min"),
|
||||||
string_literal("pointer_cast"),
|
string_literal("pointer_cast"),
|
||||||
string_literal("pointer_from_int"),
|
string_literal("pointer_from_int"),
|
||||||
string_literal("select"),
|
string_literal("select"),
|
||||||
@ -1241,6 +1245,7 @@ fn Value* parse_aggregate_initialization(Module* module, Scope* scope, ValueBuil
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto field_index = field_count;
|
auto field_index = field_count;
|
||||||
|
auto checkpoint = get_checkpoint(module);
|
||||||
if (consume_character_if_match(module, '.'))
|
if (consume_character_if_match(module, '.'))
|
||||||
{
|
{
|
||||||
auto name = parse_identifier(module);
|
auto name = parse_identifier(module);
|
||||||
@ -1530,6 +1535,37 @@ fn Value* parse_left(Module* module, Scope* scope, ValueBuilder builder)
|
|||||||
{
|
{
|
||||||
trap();
|
trap();
|
||||||
} break;
|
} break;
|
||||||
|
case ValueIntrinsic::min:
|
||||||
|
case ValueIntrinsic::max:
|
||||||
|
{
|
||||||
|
skip_space(module);
|
||||||
|
expect_character(module, left_parenthesis);
|
||||||
|
skip_space(module);
|
||||||
|
auto left = parse_value(module, scope, {});
|
||||||
|
skip_space(module);
|
||||||
|
expect_character(module, ',');
|
||||||
|
skip_space(module);
|
||||||
|
auto right = parse_value(module, scope, {});
|
||||||
|
skip_space(module);
|
||||||
|
expect_character(module, right_parenthesis);
|
||||||
|
|
||||||
|
BinaryId binary_id;
|
||||||
|
switch (intrinsic)
|
||||||
|
{
|
||||||
|
case ValueIntrinsic::max: binary_id = BinaryId::max; break;
|
||||||
|
case ValueIntrinsic::min: binary_id = BinaryId::min; break;
|
||||||
|
default: unreachable();
|
||||||
|
}
|
||||||
|
|
||||||
|
*result = {
|
||||||
|
.binary = {
|
||||||
|
.left = left,
|
||||||
|
.right = right,
|
||||||
|
.id = binary_id,
|
||||||
|
},
|
||||||
|
.id = ValueId::binary,
|
||||||
|
};
|
||||||
|
} break;
|
||||||
case ValueIntrinsic::count: unreachable();
|
case ValueIntrinsic::count: unreachable();
|
||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
@ -1540,7 +1576,26 @@ fn Value* parse_left(Module* module, Scope* scope, ValueBuilder builder)
|
|||||||
|
|
||||||
skip_space(module);
|
skip_space(module);
|
||||||
|
|
||||||
if (module->content[module->offset] == '.')
|
auto checkpoint = get_checkpoint(module);
|
||||||
|
bool is_aggregate_initialization = false;
|
||||||
|
if (consume_character_if_match(module, '.'))
|
||||||
|
{
|
||||||
|
auto identifier = parse_identifier(module);
|
||||||
|
|
||||||
|
skip_space(module);
|
||||||
|
is_aggregate_initialization = consume_character_if_match(module, '=');
|
||||||
|
if (!is_aggregate_initialization)
|
||||||
|
{
|
||||||
|
if (!consume_character_if_match(module, ','))
|
||||||
|
{
|
||||||
|
report_error();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set_checkpoint(module, checkpoint);
|
||||||
|
|
||||||
|
if (is_aggregate_initialization)
|
||||||
{
|
{
|
||||||
result = parse_aggregate_initialization(module, scope, builder, right_bracket);
|
result = parse_aggregate_initialization(module, scope, builder, right_bracket);
|
||||||
}
|
}
|
||||||
@ -2746,6 +2801,33 @@ fn String parse_name(Module* module)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct FunctionTypeCreate
|
||||||
|
{
|
||||||
|
Type* semantic_return_type;
|
||||||
|
Slice<Type*> semantic_argument_types;
|
||||||
|
CallingConvention calling_convention;
|
||||||
|
bool is_variable_arguments;
|
||||||
|
};
|
||||||
|
|
||||||
|
fn Type* get_function_type(Module* module, FunctionTypeCreate create)
|
||||||
|
{
|
||||||
|
Type* last_function_type = module->first_function_type;
|
||||||
|
|
||||||
|
while (last_function_type)
|
||||||
|
{
|
||||||
|
trap();
|
||||||
|
|
||||||
|
if (!last_function_type->next)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
last_function_type = last_function_type->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
trap();
|
||||||
|
}
|
||||||
|
|
||||||
void parse(Module* module)
|
void parse(Module* module)
|
||||||
{
|
{
|
||||||
auto scope = &module->scope;
|
auto scope = &module->scope;
|
||||||
@ -2823,16 +2905,23 @@ void parse(Module* module)
|
|||||||
|
|
||||||
auto global_name = parse_identifier(module);
|
auto global_name = parse_identifier(module);
|
||||||
|
|
||||||
|
Global* global_forward_declaration = 0;
|
||||||
Global* last_global = module->first_global;
|
Global* last_global = module->first_global;
|
||||||
while (last_global)
|
while (last_global)
|
||||||
{
|
{
|
||||||
if (global_name.equal(last_global->variable.name))
|
if (global_name.equal(last_global->variable.name))
|
||||||
{
|
{
|
||||||
report_error();
|
global_forward_declaration = last_global;
|
||||||
}
|
if (last_global->variable.storage->id != ValueId::forward_declared_function)
|
||||||
|
{
|
||||||
|
report_error();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (last_global->linkage == Linkage::external)
|
||||||
|
{
|
||||||
|
report_error();
|
||||||
|
}
|
||||||
|
|
||||||
if (!last_global->next)
|
|
||||||
{
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2840,14 +2929,14 @@ void parse(Module* module)
|
|||||||
}
|
}
|
||||||
|
|
||||||
Type* type_it = module->scope.types.first;
|
Type* type_it = module->scope.types.first;
|
||||||
Type* forward_declaration = 0;
|
Type* type_forward_declaration = 0;
|
||||||
while (type_it)
|
while (type_it)
|
||||||
{
|
{
|
||||||
if (global_name.equal(type_it->name))
|
if (global_name.equal(type_it->name))
|
||||||
{
|
{
|
||||||
if (type_it->id == TypeId::forward_declaration)
|
if (type_it->id == TypeId::forward_declaration)
|
||||||
{
|
{
|
||||||
forward_declaration = type_it;
|
type_forward_declaration = type_it;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -2924,6 +3013,12 @@ void parse(Module* module)
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto global_keyword = (GlobalKeyword)i;
|
auto global_keyword = (GlobalKeyword)i;
|
||||||
|
|
||||||
|
if (global_forward_declaration && global_keyword != GlobalKeyword::function)
|
||||||
|
{
|
||||||
|
report_error();
|
||||||
|
}
|
||||||
|
|
||||||
switch (global_keyword)
|
switch (global_keyword)
|
||||||
{
|
{
|
||||||
case GlobalKeyword::bits:
|
case GlobalKeyword::bits:
|
||||||
@ -3292,37 +3387,48 @@ void parse(Module* module)
|
|||||||
|
|
||||||
auto is_declaration = consume_character_if_match(module, ';');
|
auto is_declaration = consume_character_if_match(module, ';');
|
||||||
|
|
||||||
auto function_type = type_allocate_init(module, {
|
Global* global = 0;
|
||||||
.function = {
|
if (global_forward_declaration)
|
||||||
.semantic_return_type = return_type,
|
{
|
||||||
.semantic_argument_types = argument_types,
|
trap();
|
||||||
.calling_convention = calling_convention,
|
}
|
||||||
.is_variable_arguments = is_variable_arguments,
|
else
|
||||||
},
|
{
|
||||||
.id = TypeId::function,
|
auto function_type = type_allocate_init(module, {
|
||||||
.name = string_literal(""),
|
.function = {
|
||||||
.scope = &module->scope,
|
.base = {
|
||||||
});
|
.semantic_return_type = return_type,
|
||||||
|
.semantic_argument_types = argument_types,
|
||||||
|
.calling_convention = calling_convention,
|
||||||
|
.is_variable_arguments = is_variable_arguments,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.id = TypeId::function,
|
||||||
|
.name = string_literal(""),
|
||||||
|
.scope = &module->scope,
|
||||||
|
});
|
||||||
|
|
||||||
auto storage = new_value(module);
|
auto storage = new_value(module);
|
||||||
*storage = {
|
*storage = {
|
||||||
.type = get_pointer_type(module, function_type),
|
.type = get_pointer_type(module, function_type),
|
||||||
.id = ValueId::external_function,
|
.id = ValueId::forward_declared_function,
|
||||||
// TODO? .kind = ValueKind::left,
|
// TODO? .kind = ValueKind::left,
|
||||||
};
|
};
|
||||||
auto global = new_global(module);
|
|
||||||
*global = {
|
global = new_global(module);
|
||||||
.variable = {
|
*global = {
|
||||||
.storage = storage,
|
.variable = {
|
||||||
.initial_value = 0,
|
.storage = storage,
|
||||||
.type = function_type,
|
.initial_value = 0,
|
||||||
.scope = scope,
|
.type = function_type,
|
||||||
.name = global_name,
|
.scope = scope,
|
||||||
.line = global_line,
|
.name = global_name,
|
||||||
.column = global_column,
|
.line = global_line,
|
||||||
},
|
.column = global_column,
|
||||||
.linkage = (is_export | is_extern) ? Linkage::external : Linkage::internal,
|
},
|
||||||
};
|
.linkage = (is_export | is_extern) ? Linkage::external : Linkage::internal,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (!is_declaration)
|
if (!is_declaration)
|
||||||
{
|
{
|
||||||
@ -3340,7 +3446,7 @@ void parse(Module* module)
|
|||||||
.storage = 0,
|
.storage = 0,
|
||||||
.initial_value = 0,
|
.initial_value = 0,
|
||||||
.type = type,
|
.type = type,
|
||||||
.scope = &storage->function.scope,
|
.scope = &global->variable.storage->function.scope,
|
||||||
.name = name,
|
.name = name,
|
||||||
.line = line,
|
.line = line,
|
||||||
.column = 0,
|
.column = 0,
|
||||||
@ -3349,7 +3455,7 @@ void parse(Module* module)
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
storage->function = {
|
global->variable.storage->function = {
|
||||||
.arguments = arguments,
|
.arguments = arguments,
|
||||||
.scope = {
|
.scope = {
|
||||||
.parent = scope,
|
.parent = scope,
|
||||||
@ -3360,9 +3466,9 @@ void parse(Module* module)
|
|||||||
.block = 0,
|
.block = 0,
|
||||||
.attributes = function_attributes,
|
.attributes = function_attributes,
|
||||||
};
|
};
|
||||||
storage->id = ValueId::function;
|
global->variable.storage->id = ValueId::function;
|
||||||
|
|
||||||
storage->function.block = parse_block(module, &storage->function.scope);
|
global->variable.storage->function.block = parse_block(module, &global->variable.storage->function.scope);
|
||||||
module->current_function = 0;
|
module->current_function = 0;
|
||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
@ -3532,9 +3638,9 @@ void parse(Module* module)
|
|||||||
skip_space(module);
|
skip_space(module);
|
||||||
|
|
||||||
Type* struct_type;
|
Type* struct_type;
|
||||||
if (forward_declaration)
|
if (type_forward_declaration)
|
||||||
{
|
{
|
||||||
struct_type = forward_declaration;
|
struct_type = type_forward_declaration;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -3646,9 +3752,9 @@ void parse(Module* module)
|
|||||||
expect_character(module, left_brace);
|
expect_character(module, left_brace);
|
||||||
|
|
||||||
Type* union_type;
|
Type* union_type;
|
||||||
if (forward_declaration)
|
if (type_forward_declaration)
|
||||||
{
|
{
|
||||||
union_type = forward_declaration;
|
union_type = type_forward_declaration;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
18
tests/bool_pair.bbb
Normal file
18
tests/bool_pair.bbb
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
BoolPair = struct
|
||||||
|
{
|
||||||
|
a: u1,
|
||||||
|
b: u1,
|
||||||
|
}
|
||||||
|
|
||||||
|
bool_pair = fn () BoolPair
|
||||||
|
{
|
||||||
|
return { .a = 0, .b = 1 };
|
||||||
|
}
|
||||||
|
|
||||||
|
[export] main = fn [cc(c)] () s32
|
||||||
|
{
|
||||||
|
>result = bool_pair();
|
||||||
|
if (result.a) #trap();
|
||||||
|
if (!result.b) #trap();
|
||||||
|
return 0;
|
||||||
|
}
|
38
tests/enum_debug_info.bbb
Normal file
38
tests/enum_debug_info.bbb
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
TypeId = enum
|
||||||
|
{
|
||||||
|
void,
|
||||||
|
noreturn,
|
||||||
|
forward_declaration,
|
||||||
|
integer,
|
||||||
|
function,
|
||||||
|
pointer,
|
||||||
|
array,
|
||||||
|
enum,
|
||||||
|
struct,
|
||||||
|
bits,
|
||||||
|
alias,
|
||||||
|
union,
|
||||||
|
unresolved,
|
||||||
|
vector,
|
||||||
|
floating_point,
|
||||||
|
enum_array,
|
||||||
|
opaque,
|
||||||
|
}
|
||||||
|
|
||||||
|
Type = struct
|
||||||
|
{
|
||||||
|
arr: [5]u32,
|
||||||
|
id: TypeId,
|
||||||
|
a: [2]u64,
|
||||||
|
b: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
[export] main = fn [cc(c)] () s32
|
||||||
|
{
|
||||||
|
>t: Type = {
|
||||||
|
.id = .integer,
|
||||||
|
zero,
|
||||||
|
};
|
||||||
|
t.arr[0] = 1;
|
||||||
|
return 0;
|
||||||
|
}
|
10
tests/min_max.bbb
Normal file
10
tests/min_max.bbb
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
[export] main = fn [cc(c)] () s32
|
||||||
|
{
|
||||||
|
>a: u32 = 1;
|
||||||
|
>b: u32 = 2;
|
||||||
|
>min = #min(a, b);
|
||||||
|
>max = #max(a, b);
|
||||||
|
if (min != a) #trap();
|
||||||
|
if (max != b) #trap();
|
||||||
|
return 0;
|
||||||
|
}
|
22
tests/return_array.bbb
Normal file
22
tests/return_array.bbb
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
SomeEnum = enum
|
||||||
|
{
|
||||||
|
a,
|
||||||
|
b,
|
||||||
|
c,
|
||||||
|
d,
|
||||||
|
e,
|
||||||
|
f,
|
||||||
|
}
|
||||||
|
|
||||||
|
foo = fn () [2]SomeEnum
|
||||||
|
{
|
||||||
|
return [ .f, .e ];
|
||||||
|
}
|
||||||
|
|
||||||
|
[export] main = fn [cc(c)] () s32
|
||||||
|
{
|
||||||
|
>result = foo();
|
||||||
|
if (result[0] != .f) #trap();
|
||||||
|
if (result[1] != .e) #trap();
|
||||||
|
return 0;
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user