extern variables and object-like macro consts
This commit is contained in:
@@ -992,6 +992,31 @@ main :: func() void {
|
||||
testing.expect(t, sentinel_error)
|
||||
}
|
||||
|
||||
@(test)
|
||||
immutable_pointer_and_slice_bindings_preserve_mutable_pointees :: proc(t: ^testing.T) {
|
||||
text := `main :: func() void {
|
||||
values [2]mut u8 = [1, 2]
|
||||
pointer *mut u8 :: (&values).ptr
|
||||
slice []mut u8 :: values[0..]
|
||||
pointer[0] = 3
|
||||
slice[1] = 4
|
||||
}
|
||||
`
|
||||
source_file := source.Source{path="test.bro", text=text}
|
||||
diagnostics := source.init_diagnostics(&source_file)
|
||||
defer source.destroy_diagnostics(&diagnostics)
|
||||
symbols := symbol.init_table()
|
||||
defer symbol.destroy_table(&symbols)
|
||||
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||
defer delete(stream.items)
|
||||
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||
defer ast.destroy_module(&ast_module)
|
||||
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||
defer hir.destroy_module(&hir_module)
|
||||
|
||||
testing.expect_value(t, len(diagnostics.items), 0)
|
||||
}
|
||||
|
||||
@(test)
|
||||
c_variadic_calls_promote_extras_and_emit_variadic_llvm :: proc(t: ^testing.T) {
|
||||
text := `variadic :: c_func(tag c_int, ...) c_int
|
||||
@@ -2118,6 +2143,14 @@ Fake_Cimport_State :: struct {
|
||||
saw_options: bool,
|
||||
}
|
||||
|
||||
Conflict_Cimport_State :: struct {
|
||||
calls: int,
|
||||
}
|
||||
|
||||
Symbol_Conflict_Cimport_State :: struct {
|
||||
calls: int,
|
||||
}
|
||||
|
||||
fake_cimport_backend :: proc(user_data: rawptr, request: cimport.Request, allocator: mem.Allocator) -> cimport.Result {
|
||||
state := (^Fake_Cimport_State)(user_data)
|
||||
state.calls += 1
|
||||
@@ -2142,10 +2175,143 @@ fake_cimport_backend :: proc(user_data: rawptr, request: cimport.Request, alloca
|
||||
variadic=true,
|
||||
reason=fmt.aprintf("", allocator=allocator),
|
||||
})
|
||||
append(&result.variables, cimport.Variable{
|
||||
name=fmt.aprintf("fake_global", allocator=allocator),
|
||||
type=cimport.Type_Id(0),
|
||||
mutable=true,
|
||||
reason=fmt.aprintf("", allocator=allocator),
|
||||
})
|
||||
append(&result.macros, cimport.Macro_Constant{
|
||||
name=fmt.aprintf("FAKE_MAGIC", allocator=allocator),
|
||||
type=cimport.Type_Id(0),
|
||||
value={kind=.Integer, type=cimport.Type_Id(0), integer=7},
|
||||
reason=fmt.aprintf("", allocator=allocator),
|
||||
})
|
||||
result.available = true
|
||||
return result
|
||||
}
|
||||
|
||||
conflict_cimport_backend :: proc(user_data: rawptr, request: cimport.Request, allocator: mem.Allocator) -> cimport.Result {
|
||||
state := (^Conflict_Cimport_State)(user_data)
|
||||
state.calls += 1
|
||||
result := cimport.init_result(allocator)
|
||||
kind := cimport.Type_Kind.C_Int
|
||||
if strings.has_suffix(request.path, "second.h") {
|
||||
kind = .C_Long
|
||||
}
|
||||
append(&result.types, cimport.Type{kind=kind, child=cimport.INVALID_TYPE})
|
||||
append(&result.variables, cimport.Variable{
|
||||
name=fmt.aprintf("conflict_global", allocator=allocator),
|
||||
type=cimport.Type_Id(0),
|
||||
mutable=true,
|
||||
reason=fmt.aprintf("", allocator=allocator),
|
||||
})
|
||||
result.available = true
|
||||
return result
|
||||
}
|
||||
|
||||
symbol_conflict_cimport_backend :: proc(user_data: rawptr, request: cimport.Request, allocator: mem.Allocator) -> cimport.Result {
|
||||
state := (^Symbol_Conflict_Cimport_State)(user_data)
|
||||
state.calls += 1
|
||||
result := cimport.init_result(allocator)
|
||||
append(&result.types, cimport.Type{kind=.C_Int, child=cimport.INVALID_TYPE})
|
||||
if strings.has_suffix(request.path, "variable.h") {
|
||||
append(&result.variables, cimport.Variable{
|
||||
name=fmt.aprintf("conflict_symbol", allocator=allocator),
|
||||
type=cimport.Type_Id(0),
|
||||
mutable=true,
|
||||
reason=fmt.aprintf("", allocator=allocator),
|
||||
})
|
||||
} else {
|
||||
append(&result.functions, cimport.Function{
|
||||
name=fmt.aprintf("conflict_symbol", allocator=allocator),
|
||||
result=cimport.Type_Id(0),
|
||||
reason=fmt.aprintf("", allocator=allocator),
|
||||
})
|
||||
}
|
||||
result.available = true
|
||||
return result
|
||||
}
|
||||
|
||||
main_conflict_cimport_backend :: proc(_: rawptr, _: cimport.Request, allocator: mem.Allocator) -> cimport.Result {
|
||||
result := cimport.init_result(allocator)
|
||||
append(&result.types, cimport.Type{kind=.C_Int, child=cimport.INVALID_TYPE})
|
||||
append(&result.variables, cimport.Variable{
|
||||
name=fmt.aprintf("main", allocator=allocator),
|
||||
type=cimport.Type_Id(0),
|
||||
mutable=true,
|
||||
reason=fmt.aprintf("", allocator=allocator),
|
||||
})
|
||||
result.available = true
|
||||
return result
|
||||
}
|
||||
|
||||
write_conflict_cimport_backend :: proc(_: rawptr, _: cimport.Request, allocator: mem.Allocator) -> cimport.Result {
|
||||
result := cimport.init_result(allocator)
|
||||
append(&result.types, cimport.Type{kind=.C_Int, child=cimport.INVALID_TYPE})
|
||||
append(&result.variables, cimport.Variable{
|
||||
name=fmt.aprintf("write", allocator=allocator),
|
||||
type=cimport.Type_Id(0),
|
||||
mutable=true,
|
||||
reason=fmt.aprintf("", allocator=allocator),
|
||||
})
|
||||
result.available = true
|
||||
return result
|
||||
}
|
||||
|
||||
count_substring_occurrences :: proc(text, needle: string) -> int {
|
||||
if len(needle) == 0 {
|
||||
return 0
|
||||
}
|
||||
count := 0
|
||||
for index := 0; index + len(needle) <= len(text); index += 1 {
|
||||
if text[index:index + len(needle)] == needle {
|
||||
count += 1
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
find_cimport_variable :: proc(result: ^cimport.Result, name: string) -> (^cimport.Variable, bool) {
|
||||
for &variable in result.variables {
|
||||
if variable.name == name {
|
||||
return &variable, true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
count_cimport_variables :: proc(result: ^cimport.Result, name: string) -> int {
|
||||
count := 0
|
||||
for variable in result.variables {
|
||||
if variable.name == name {
|
||||
count += 1
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
find_cimport_macro :: proc(result: ^cimport.Result, name: string) -> (^cimport.Macro_Constant, bool) {
|
||||
for ¯o in result.macros {
|
||||
if macro.name == name {
|
||||
return ¯o, true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
cimport_has_named_result :: proc(result: ^cimport.Result, name: string) -> bool {
|
||||
if _, ok := find_cimport_macro(result, name); ok {
|
||||
return true
|
||||
}
|
||||
for item in result.unsupported {
|
||||
if item.name == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@(test)
|
||||
cimport_backend_is_replaceable :: proc(t: ^testing.T) {
|
||||
state := Fake_Cimport_State{available=true}
|
||||
@@ -2156,10 +2322,238 @@ cimport_backend_is_replaceable :: proc(t: ^testing.T) {
|
||||
testing.expect(t, result.available)
|
||||
testing.expect_value(t, state.calls, 1)
|
||||
testing.expect_value(t, len(result.functions), 1)
|
||||
testing.expect_value(t, len(result.variables), 1)
|
||||
testing.expect_value(t, len(result.macros), 1)
|
||||
testing.expect_value(t, result.functions[0].name, "fake_value")
|
||||
testing.expect_value(t, result.variables[0].name, "fake_global")
|
||||
testing.expect_value(t, result.macros[0].name, "FAKE_MAGIC")
|
||||
testing.expect(t, result.functions[0].variadic)
|
||||
}
|
||||
|
||||
@(test)
|
||||
libclang_import_preserves_external_object_and_final_macro_semantics :: proc(t: ^testing.T) {
|
||||
options := cimport.Options{
|
||||
include_paths=[]string{"examples/interop/header/include"},
|
||||
defines=[]string{"BROLANG_FEATURE"},
|
||||
}
|
||||
result := cimport.import_header(
|
||||
options,
|
||||
"examples/interop/header/include/native.h",
|
||||
target.DEFAULT,
|
||||
)
|
||||
defer cimport.destroy_result(&result)
|
||||
|
||||
testing.expect(t, result.available)
|
||||
testing.expect_value(t, result.error_message, "")
|
||||
|
||||
tls, found_tls := find_cimport_variable(&result, "imported_tls_global")
|
||||
testing.expect(t, found_tls)
|
||||
if found_tls {
|
||||
testing.expect(t, strings.contains(tls.reason, "thread-local C variables are not supported"))
|
||||
}
|
||||
|
||||
const_array, found_const_array := find_cimport_variable(&result, "imported_const_array")
|
||||
testing.expect(t, found_const_array)
|
||||
if found_const_array {
|
||||
testing.expect(t, !const_array.mutable)
|
||||
testing.expect(t, const_array.type != cimport.INVALID_TYPE)
|
||||
if const_array.type != cimport.INVALID_TYPE {
|
||||
array_type := result.types[const_array.type]
|
||||
testing.expect_value(t, array_type.kind, cimport.Type_Kind.Array)
|
||||
testing.expect(t, array_type.child != cimport.INVALID_TYPE)
|
||||
if array_type.child != cimport.INVALID_TYPE {
|
||||
testing.expect_value(t, result.types[array_type.child].kind, cimport.Type_Kind.C_Int)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typedef_const_array, found_typedef_const_array := find_cimport_variable(
|
||||
&result, "imported_typedef_const_array",
|
||||
)
|
||||
testing.expect(t, found_typedef_const_array)
|
||||
if found_typedef_const_array {
|
||||
testing.expect(t, !typedef_const_array.mutable)
|
||||
testing.expect_value(t, typedef_const_array.reason, "")
|
||||
testing.expect(t, typedef_const_array.type != cimport.INVALID_TYPE)
|
||||
if typedef_const_array.type != cimport.INVALID_TYPE {
|
||||
array_type := result.types[typedef_const_array.type]
|
||||
testing.expect_value(t, array_type.kind, cimport.Type_Kind.Array)
|
||||
testing.expect(t, !array_type.mutable)
|
||||
testing.expect_value(t, array_type.count, u64(2))
|
||||
}
|
||||
}
|
||||
|
||||
redeclared_array, found_redeclared_array := find_cimport_variable(
|
||||
&result, "imported_redeclared_array",
|
||||
)
|
||||
testing.expect(t, found_redeclared_array)
|
||||
testing.expect_value(t, count_cimport_variables(&result, "imported_redeclared_array"), 1)
|
||||
if found_redeclared_array {
|
||||
testing.expect_value(t, redeclared_array.reason, "")
|
||||
testing.expect(t, redeclared_array.type != cimport.INVALID_TYPE)
|
||||
if redeclared_array.type != cimport.INVALID_TYPE {
|
||||
array_type := result.types[redeclared_array.type]
|
||||
testing.expect_value(t, array_type.kind, cimport.Type_Kind.Array)
|
||||
testing.expect_value(t, array_type.count, u64(4))
|
||||
}
|
||||
}
|
||||
|
||||
repeated, found_repeated := find_cimport_macro(&result, "IMPORTED_REPEAT")
|
||||
testing.expect(t, found_repeated)
|
||||
if found_repeated {
|
||||
testing.expect_value(t, repeated.value.integer, u64(123))
|
||||
}
|
||||
testing.expect(t, !cimport_has_named_result(&result, "IMPORTED_GUARDED"))
|
||||
testing.expect(t, !cimport_has_named_result(&result, "IMPORTED_ONCE"))
|
||||
|
||||
negative_decimal, found_negative_decimal := find_cimport_macro(&result, "IMPORTED_NEG_DECIMAL")
|
||||
testing.expect(t, found_negative_decimal)
|
||||
if found_negative_decimal {
|
||||
testing.expect_value(t, result.types[negative_decimal.type].kind, cimport.Type_Kind.C_Long)
|
||||
testing.expect_value(t, negative_decimal.value.integer, u64(2147483648))
|
||||
testing.expect(t, negative_decimal.value.negative)
|
||||
}
|
||||
|
||||
negative_hex, found_negative_hex := find_cimport_macro(&result, "IMPORTED_NEG_HEX")
|
||||
testing.expect(t, found_negative_hex)
|
||||
if found_negative_hex {
|
||||
testing.expect_value(t, result.types[negative_hex.type].kind, cimport.Type_Kind.C_Uint)
|
||||
testing.expect_value(t, negative_hex.value.integer, u64(0x80000000))
|
||||
testing.expect(t, !negative_hex.value.negative)
|
||||
}
|
||||
|
||||
negative_uint, found_negative_uint := find_cimport_macro(&result, "IMPORTED_NEG_UINT")
|
||||
testing.expect(t, found_negative_uint)
|
||||
if found_negative_uint {
|
||||
testing.expect_value(t, result.types[negative_uint.type].kind, cimport.Type_Kind.C_Uint)
|
||||
testing.expect_value(t, negative_uint.value.integer, u64(0xffffffff))
|
||||
testing.expect(t, !negative_uint.value.negative)
|
||||
}
|
||||
|
||||
conversions, found_conversions := find_cimport_macro(&result, "IMPORTED_CONVERSIONS")
|
||||
testing.expect(t, found_conversions)
|
||||
if found_conversions {
|
||||
testing.expect_value(t, len(conversions.values), 5)
|
||||
expected_kinds := [?]cimport.Type_Kind{
|
||||
.C_Int,
|
||||
.C_Double,
|
||||
.C_Int,
|
||||
.C_Float,
|
||||
.C_Int,
|
||||
}
|
||||
for value, index in conversions.values {
|
||||
testing.expect(t, value.type != cimport.INVALID_TYPE)
|
||||
if value.type != cimport.INVALID_TYPE {
|
||||
testing.expect_value(t, result.types[value.type].kind, expected_kinds[index])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
final_macros_override_same_named_c_value_declarations :: proc(t: ^testing.T) {
|
||||
sources := source.init_store()
|
||||
defer source.destroy_store(&sources)
|
||||
diagnostics := source.init_store_diagnostics(&sources)
|
||||
defer source.destroy_diagnostics(&diagnostics)
|
||||
symbols := symbol.init_table()
|
||||
defer symbol.destroy_table(&symbols)
|
||||
c_options := cimport.Options{
|
||||
include_paths=[]string{"examples/interop/header/include"},
|
||||
defines=[]string{"BROLANG_FEATURE"},
|
||||
}
|
||||
module, loaded := loader.load(
|
||||
"examples/interop/header/app",
|
||||
&sources,
|
||||
&diagnostics,
|
||||
&symbols,
|
||||
c_options=c_options,
|
||||
)
|
||||
defer ast.destroy_module(&module)
|
||||
testing.expect(t, loaded)
|
||||
|
||||
hir_module := checker.check(&module, &diagnostics, &symbols)
|
||||
defer hir.destroy_module(&hir_module)
|
||||
ir_module := lower.lower(&hir_module)
|
||||
defer ir.destroy_module(&ir_module)
|
||||
llvm_text := llvm.emit(&ir_module, &diagnostics, &symbols)
|
||||
defer delete(llvm_text)
|
||||
|
||||
testing.expect_value(t, len(diagnostics.items), 0)
|
||||
testing.expect(t, !strings.contains(llvm_text, "@IMPORTED_SHADOW_OBJECT = external"))
|
||||
testing.expect(t, !strings.contains(llvm_text, "declare i32 @IMPORTED_SHADOW_FUNCTION("))
|
||||
testing.expect(t, !strings.contains(llvm_text, "@IMPORTED_SHADOW_UNSUPPORTED = external"))
|
||||
testing.expect(t, !strings.contains(llvm_text, "@IMPORTED_EMPTY_SHADOW = external"))
|
||||
}
|
||||
|
||||
@(test)
|
||||
static_inline_c_functions_route_through_generated_trampolines :: proc(t: ^testing.T) {
|
||||
sources := source.init_store()
|
||||
defer source.destroy_store(&sources)
|
||||
diagnostics := source.init_store_diagnostics(&sources)
|
||||
defer source.destroy_diagnostics(&diagnostics)
|
||||
symbols := symbol.init_table()
|
||||
defer symbol.destroy_table(&symbols)
|
||||
c_options := cimport.Options{
|
||||
include_paths=[]string{"examples/interop/header/include"},
|
||||
defines=[]string{"BROLANG_FEATURE"},
|
||||
}
|
||||
module, loaded := loader.load(
|
||||
"examples/interop/header/app",
|
||||
&sources,
|
||||
&diagnostics,
|
||||
&symbols,
|
||||
c_options=c_options,
|
||||
)
|
||||
defer ast.destroy_module(&module)
|
||||
testing.expect(t, loaded)
|
||||
|
||||
// Symbols are namespaced by a header-path hash, so match by suffix.
|
||||
scalar_symbol := ""
|
||||
record_symbol := ""
|
||||
for trampoline in module.c_trampolines {
|
||||
testing.expect(t, strings.has_prefix(trampoline.symbol, "__brolang_inline_"))
|
||||
if strings.has_suffix(trampoline.symbol, "_imported_inline") {
|
||||
scalar_symbol = trampoline.symbol
|
||||
}
|
||||
if strings.has_suffix(trampoline.symbol, "_imported_inline_record") {
|
||||
record_symbol = trampoline.symbol
|
||||
}
|
||||
// The variadic static inline is unsupported and must not be wrapped.
|
||||
testing.expect(t, !strings.has_suffix(trampoline.symbol, "_imported_inline_variadic"))
|
||||
}
|
||||
testing.expect(t, scalar_symbol != "")
|
||||
testing.expect(t, record_symbol != "")
|
||||
|
||||
// A static inline whose signature translates but is rejected by the loader's
|
||||
// by-value layout checks keeps its cimport-assigned link_name yet must not
|
||||
// emit a wrapper — it is uncallable, so the wrapper would be dead code.
|
||||
bad_layout_link := ""
|
||||
for function in module.functions {
|
||||
if symbol.resolve(&symbols, function.name) == "imported_inline_bad_layout" {
|
||||
testing.expect(t, len(function.unsupported_reason) > 0)
|
||||
bad_layout_link = function.link_name
|
||||
}
|
||||
}
|
||||
testing.expect(t, bad_layout_link != "") // cimport did generate a wrapper symbol
|
||||
for trampoline in module.c_trampolines {
|
||||
testing.expect(t, trampoline.symbol != bad_layout_link)
|
||||
}
|
||||
|
||||
hir_module := checker.check(&module, &diagnostics, &symbols)
|
||||
defer hir.destroy_module(&hir_module)
|
||||
ir_module := lower.lower(&hir_module)
|
||||
defer ir.destroy_module(&ir_module)
|
||||
llvm_text := llvm.emit(&ir_module, &diagnostics, &symbols)
|
||||
defer delete(llvm_text)
|
||||
|
||||
testing.expect_value(t, len(diagnostics.items), 0)
|
||||
testing.expect(t, strings.contains(llvm_text, fmt.tprintf("@%s(", scalar_symbol)))
|
||||
testing.expect(t, strings.contains(llvm_text, fmt.tprintf("@%s(", record_symbol)))
|
||||
// The internal-linkage C symbol itself is never declared or called directly.
|
||||
testing.expect(t, !strings.contains(llvm_text, "@imported_inline("))
|
||||
}
|
||||
|
||||
@(test)
|
||||
loader_injects_and_caches_cimport_backend_per_compilation :: proc(t: ^testing.T) {
|
||||
sources := source.init_store()
|
||||
@@ -2195,6 +2589,336 @@ loader_injects_and_caches_cimport_backend_per_compilation :: proc(t: ^testing.T)
|
||||
found_variadic = found_variadic || function.variadic
|
||||
}
|
||||
testing.expect(t, found_variadic)
|
||||
found_external := false
|
||||
found_macro := false
|
||||
for global in module.globals {
|
||||
name := symbol.resolve(&symbols, global.name)
|
||||
found_external = found_external || (name == "fake_global" && global.external && global.writable)
|
||||
found_macro = found_macro || name == "FAKE_MAGIC"
|
||||
}
|
||||
testing.expect(t, found_external)
|
||||
testing.expect(t, found_macro)
|
||||
}
|
||||
|
||||
@(test)
|
||||
conflicting_external_c_globals_are_diagnosed_and_deduped :: proc(t: ^testing.T) {
|
||||
sources := source.init_store()
|
||||
defer source.destroy_store(&sources)
|
||||
diagnostics := source.init_store_diagnostics(&sources)
|
||||
defer source.destroy_diagnostics(&diagnostics)
|
||||
symbols := symbol.init_table()
|
||||
defer symbol.destroy_table(&symbols)
|
||||
state := Conflict_Cimport_State{}
|
||||
options := cimport.Options{backend={import_header=conflict_cimport_backend, user_data=&state}}
|
||||
module, loaded := loader.load(
|
||||
"examples/interop/header_conflict",
|
||||
&sources,
|
||||
&diagnostics,
|
||||
&symbols,
|
||||
c_options=options,
|
||||
)
|
||||
defer ast.destroy_module(&module)
|
||||
testing.expect(t, loaded)
|
||||
testing.expect_value(t, state.calls, 2)
|
||||
|
||||
hir_module := checker.check(&module, &diagnostics, &symbols)
|
||||
defer hir.destroy_module(&hir_module)
|
||||
ir_module := lower.lower(&hir_module)
|
||||
defer ir.destroy_module(&ir_module)
|
||||
llvm_text := llvm.emit(&ir_module, &diagnostics, &symbols)
|
||||
defer delete(llvm_text)
|
||||
|
||||
found_conflict := false
|
||||
for diagnostic in diagnostics.items {
|
||||
if strings.contains(diagnostic.message, "conflicting external C variable declarations for 'conflict_global'") {
|
||||
found_conflict = true
|
||||
}
|
||||
}
|
||||
testing.expect(t, found_conflict)
|
||||
testing.expect_value(t, count_substring_occurrences(llvm_text, "@conflict_global = external global"), 1)
|
||||
testing.expect(t, strings.contains(llvm_text, "@conflict_global = external global i32"))
|
||||
testing.expect(t, !strings.contains(llvm_text, "@conflict_global = external global i64"))
|
||||
}
|
||||
|
||||
@(test)
|
||||
external_c_global_and_function_link_name_conflict_is_diagnosed :: proc(t: ^testing.T) {
|
||||
sources := source.init_store()
|
||||
defer source.destroy_store(&sources)
|
||||
diagnostics := source.init_store_diagnostics(&sources)
|
||||
defer source.destroy_diagnostics(&diagnostics)
|
||||
symbols := symbol.init_table()
|
||||
defer symbol.destroy_table(&symbols)
|
||||
state := Symbol_Conflict_Cimport_State{}
|
||||
options := cimport.Options{backend={import_header=symbol_conflict_cimport_backend, user_data=&state}}
|
||||
module, loaded := loader.load(
|
||||
"examples/interop/header_symbol_conflict",
|
||||
&sources,
|
||||
&diagnostics,
|
||||
&symbols,
|
||||
c_options=options,
|
||||
)
|
||||
defer ast.destroy_module(&module)
|
||||
testing.expect(t, loaded)
|
||||
testing.expect_value(t, state.calls, 2)
|
||||
|
||||
hir_module := checker.check(&module, &diagnostics, &symbols)
|
||||
defer hir.destroy_module(&hir_module)
|
||||
ir_module := lower.lower(&hir_module)
|
||||
defer ir.destroy_module(&ir_module)
|
||||
llvm_text := llvm.emit(&ir_module, &diagnostics, &symbols)
|
||||
defer delete(llvm_text)
|
||||
|
||||
found_conflict := false
|
||||
for diagnostic in diagnostics.items {
|
||||
if strings.contains(diagnostic.message, "external C variable 'conflict_symbol' conflicts with a C function declaration") {
|
||||
found_conflict = true
|
||||
}
|
||||
}
|
||||
testing.expect(t, found_conflict)
|
||||
testing.expect(t, !strings.contains(llvm_text, "@conflict_symbol = external global"))
|
||||
testing.expect(t, strings.contains(llvm_text, "declare i32 @conflict_symbol()"))
|
||||
testing.expect(t, !strings.contains(llvm_text, "load i32, ptr @conflict_symbol"))
|
||||
}
|
||||
|
||||
@(test)
|
||||
external_c_global_named_main_is_omitted_for_root_entry_point :: proc(t: ^testing.T) {
|
||||
sources := source.init_store()
|
||||
defer source.destroy_store(&sources)
|
||||
diagnostics := source.init_store_diagnostics(&sources)
|
||||
defer source.destroy_diagnostics(&diagnostics)
|
||||
symbols := symbol.init_table()
|
||||
defer symbol.destroy_table(&symbols)
|
||||
options := cimport.Options{backend={import_header=main_conflict_cimport_backend}}
|
||||
module, loaded := loader.load(
|
||||
"examples/interop/header_main_conflict",
|
||||
&sources,
|
||||
&diagnostics,
|
||||
&symbols,
|
||||
c_options=options,
|
||||
)
|
||||
defer ast.destroy_module(&module)
|
||||
testing.expect(t, loaded)
|
||||
|
||||
hir_module := checker.check(&module, &diagnostics, &symbols)
|
||||
defer hir.destroy_module(&hir_module)
|
||||
ir_module := lower.lower(&hir_module)
|
||||
defer ir.destroy_module(&ir_module)
|
||||
llvm_text := llvm.emit(&ir_module, &diagnostics, &symbols)
|
||||
defer delete(llvm_text)
|
||||
|
||||
found_conflict := false
|
||||
for diagnostic in diagnostics.items {
|
||||
found_conflict =
|
||||
found_conflict ||
|
||||
strings.contains(
|
||||
diagnostic.message,
|
||||
"external C variable 'main' conflicts with the program entry point",
|
||||
)
|
||||
}
|
||||
testing.expect(t, found_conflict)
|
||||
testing.expect_value(t, count_substring_occurrences(llvm_text, "define i32 @main("), 1)
|
||||
testing.expect(t, !strings.contains(llvm_text, "@main = external"))
|
||||
}
|
||||
|
||||
@(test)
|
||||
external_c_global_named_main_is_omitted_for_synthesized_entry_point :: proc(t: ^testing.T) {
|
||||
sources := source.init_store()
|
||||
defer source.destroy_store(&sources)
|
||||
diagnostics := source.init_store_diagnostics(&sources)
|
||||
defer source.destroy_diagnostics(&diagnostics)
|
||||
symbols := symbol.init_table()
|
||||
defer symbol.destroy_table(&symbols)
|
||||
options := cimport.Options{backend={import_header=main_conflict_cimport_backend}}
|
||||
module, loaded := loader.load(
|
||||
"examples/interop/header_main_conflict_missing",
|
||||
&sources,
|
||||
&diagnostics,
|
||||
&symbols,
|
||||
c_options=options,
|
||||
)
|
||||
defer ast.destroy_module(&module)
|
||||
testing.expect(t, loaded)
|
||||
|
||||
hir_module := checker.check(&module, &diagnostics, &symbols)
|
||||
defer hir.destroy_module(&hir_module)
|
||||
ir_module := lower.lower(&hir_module)
|
||||
defer ir.destroy_module(&ir_module)
|
||||
llvm_text := llvm.emit(&ir_module, &diagnostics, &symbols)
|
||||
defer delete(llvm_text)
|
||||
|
||||
found_conflict := false
|
||||
found_missing_main := false
|
||||
for diagnostic in diagnostics.items {
|
||||
found_conflict =
|
||||
found_conflict ||
|
||||
strings.contains(
|
||||
diagnostic.message,
|
||||
"external C variable 'main' conflicts with the program entry point",
|
||||
)
|
||||
found_missing_main =
|
||||
found_missing_main ||
|
||||
strings.contains(diagnostic.message, "missing or unusable main function")
|
||||
}
|
||||
testing.expect(t, found_conflict)
|
||||
testing.expect(t, found_missing_main)
|
||||
testing.expect_value(t, count_substring_occurrences(llvm_text, "define i32 @main("), 1)
|
||||
testing.expect(t, !strings.contains(llvm_text, "@main = external"))
|
||||
}
|
||||
|
||||
@(test)
|
||||
external_c_global_named_write_is_omitted_for_compiler_runtime :: proc(t: ^testing.T) {
|
||||
sources := source.init_store()
|
||||
defer source.destroy_store(&sources)
|
||||
diagnostics := source.init_store_diagnostics(&sources)
|
||||
defer source.destroy_diagnostics(&diagnostics)
|
||||
symbols := symbol.init_table()
|
||||
defer symbol.destroy_table(&symbols)
|
||||
options := cimport.Options{backend={import_header=write_conflict_cimport_backend}}
|
||||
module, loaded := loader.load(
|
||||
"examples/interop/header_write_conflict",
|
||||
&sources,
|
||||
&diagnostics,
|
||||
&symbols,
|
||||
c_options=options,
|
||||
)
|
||||
defer ast.destroy_module(&module)
|
||||
testing.expect(t, loaded)
|
||||
|
||||
hir_module := checker.check(&module, &diagnostics, &symbols)
|
||||
defer hir.destroy_module(&hir_module)
|
||||
ir_module := lower.lower(&hir_module)
|
||||
defer ir.destroy_module(&ir_module)
|
||||
llvm_text := llvm.emit(&ir_module, &diagnostics, &symbols)
|
||||
defer delete(llvm_text)
|
||||
|
||||
found_conflict := false
|
||||
for diagnostic in diagnostics.items {
|
||||
found_conflict =
|
||||
found_conflict ||
|
||||
strings.contains(
|
||||
diagnostic.message,
|
||||
"external C variable 'write' conflicts with the compiler runtime",
|
||||
)
|
||||
}
|
||||
testing.expect(t, found_conflict)
|
||||
testing.expect_value(t, count_substring_occurrences(llvm_text, "declare i64 @write("), 1)
|
||||
testing.expect(t, !strings.contains(llvm_text, "@write = external"))
|
||||
testing.expect(t, strings.contains(llvm_text, "call void @bro.trap"))
|
||||
}
|
||||
|
||||
@(test)
|
||||
tls_reference_and_const_external_array_assignment_are_diagnosed :: proc(t: ^testing.T) {
|
||||
sources := source.init_store()
|
||||
defer source.destroy_store(&sources)
|
||||
diagnostics := source.init_store_diagnostics(&sources)
|
||||
defer source.destroy_diagnostics(&diagnostics)
|
||||
symbols := symbol.init_table()
|
||||
defer symbol.destroy_table(&symbols)
|
||||
c_options := cimport.Options{include_paths=[]string{"examples/interop/header/include"}}
|
||||
module, loaded := loader.load(
|
||||
"examples/interop/header_unsupported",
|
||||
&sources,
|
||||
&diagnostics,
|
||||
&symbols,
|
||||
c_options=c_options,
|
||||
)
|
||||
defer ast.destroy_module(&module)
|
||||
testing.expect(t, loaded)
|
||||
|
||||
hir_module := checker.check(&module, &diagnostics, &symbols)
|
||||
defer hir.destroy_module(&hir_module)
|
||||
ir_module := lower.lower(&hir_module)
|
||||
defer ir.destroy_module(&ir_module)
|
||||
llvm_text := llvm.emit(&ir_module, &diagnostics, &symbols)
|
||||
defer delete(llvm_text)
|
||||
|
||||
found_tls := false
|
||||
found_excess_aggregate := false
|
||||
found_signed_narrow := false
|
||||
found_shadowed_unsupported := false
|
||||
found_float_overflow := false
|
||||
found_empty_shadow := false
|
||||
found_inline_variadic := false
|
||||
not_writable_count := 0
|
||||
for diagnostic in diagnostics.items {
|
||||
found_tls =
|
||||
found_tls ||
|
||||
strings.contains(
|
||||
diagnostic.message,
|
||||
"C declaration 'imported_tls_global' is unavailable: thread-local C variables are not supported",
|
||||
)
|
||||
found_excess_aggregate =
|
||||
found_excess_aggregate ||
|
||||
strings.contains(
|
||||
diagnostic.message,
|
||||
"C declaration 'IMPORTED_TOO_MANY_COLOR' is unavailable: C macro aggregate initializer is not representable",
|
||||
)
|
||||
found_signed_narrow =
|
||||
found_signed_narrow ||
|
||||
strings.contains(
|
||||
diagnostic.message,
|
||||
"C declaration 'IMPORTED_SIGNED_NARROW_BAD' is unavailable: C macro aggregate initializer is not representable",
|
||||
)
|
||||
found_shadowed_unsupported =
|
||||
found_shadowed_unsupported ||
|
||||
strings.contains(
|
||||
diagnostic.message,
|
||||
"C declaration 'IMPORTED_SHADOW_UNSUPPORTED' is unavailable: C macro is not a supported constant",
|
||||
)
|
||||
found_float_overflow =
|
||||
found_float_overflow ||
|
||||
strings.contains(
|
||||
diagnostic.message,
|
||||
"C declaration 'IMPORTED_FLOAT_OVERFLOW' is unavailable: C macro is not a supported constant",
|
||||
)
|
||||
found_empty_shadow =
|
||||
found_empty_shadow ||
|
||||
strings.contains(
|
||||
diagnostic.message,
|
||||
"C declaration 'IMPORTED_EMPTY_SHADOW' is unavailable: C macro has no replacement value",
|
||||
)
|
||||
found_inline_variadic =
|
||||
found_inline_variadic ||
|
||||
strings.contains(
|
||||
diagnostic.message,
|
||||
"C declaration 'imported_inline_variadic' is unavailable: variadic static inline C functions are not supported",
|
||||
)
|
||||
if strings.contains(diagnostic.message, "assignment target is not writable") {
|
||||
not_writable_count += 1
|
||||
}
|
||||
}
|
||||
testing.expect(t, found_tls)
|
||||
testing.expect(t, found_excess_aggregate)
|
||||
testing.expect(t, found_signed_narrow)
|
||||
testing.expect(t, found_shadowed_unsupported)
|
||||
testing.expect(t, found_float_overflow)
|
||||
testing.expect(t, found_empty_shadow)
|
||||
testing.expect(t, found_inline_variadic)
|
||||
testing.expect(t, not_writable_count >= 5)
|
||||
testing.expect(t, !strings.contains(llvm_text, "@IMPORTED_EMPTY_SHADOW = external"))
|
||||
testing.expect(
|
||||
t,
|
||||
strings.contains(
|
||||
llvm_text,
|
||||
"@imported_typedef_const_array = external constant [2 x i32]",
|
||||
),
|
||||
)
|
||||
testing.expect_value(
|
||||
t,
|
||||
count_substring_occurrences(
|
||||
llvm_text,
|
||||
"@imported_redeclared_array = external global [4 x i32]",
|
||||
),
|
||||
1,
|
||||
)
|
||||
testing.expect(
|
||||
t,
|
||||
!strings.contains(
|
||||
llvm_text,
|
||||
"ptr @imported_const_array_record, i64 0",
|
||||
),
|
||||
)
|
||||
testing.expect(t, !strings.contains(llvm_text, "@IMPORTED_SHADOW_UNSUPPORTED = external"))
|
||||
}
|
||||
|
||||
@(test)
|
||||
|
||||
Reference in New Issue
Block a user