extern variables and object-like macro consts
This commit is contained in:
@@ -69,6 +69,99 @@
|
|||||||
- concrete `c_func` declarations/definitions can be passed as callback values
|
- concrete `c_func` declarations/definitions can be passed as callback values
|
||||||
- postfix calls through non-null function pointers, including `callback?(...)`
|
- postfix calls through non-null function pointers, including `callback?(...)`
|
||||||
- fixed and C-variadic callback ABI emission through LLVM indirect calls
|
- fixed and C-variadic callback ABI emission through LLVM indirect calls
|
||||||
- external variables
|
- external variables (implemented)
|
||||||
- macros and static inline functions
|
- imported external C object variables lower to direct LLVM external global references
|
||||||
- exporting brolang functions to c
|
- top-level `const` object variables are read-only from brolang
|
||||||
|
- mutable external scalars/records can be assigned through qualified package globals
|
||||||
|
- unsupported variable types remain lazy diagnostics when referenced
|
||||||
|
- object-like macro constants (implemented)
|
||||||
|
- scalar integer/float literal macros import as immutable globals
|
||||||
|
- `CLITERAL(Type){ ... }` / `(Type){ ... }` record literal macros import as immutable globals
|
||||||
|
- function-like macros and non-literal macro expressions remain unsupported
|
||||||
|
- static inline functions (implemented)
|
||||||
|
|
||||||
|
- 5. control flow
|
||||||
|
- boolean expressions
|
||||||
|
- operators: `and`, `or`, `!`
|
||||||
|
- lazy evaluation / short-circuit evaluation
|
||||||
|
- if statements. example: `if condition { ... } else if { ... } else { ... }`
|
||||||
|
- conditional unwrapping for optionals (`?T`): `if val |v| { ... } else { ... }` - unwrap `val` into `v` if it is not `none`
|
||||||
|
- conditional unwrapping with guard clause: `if val |v : v >= 10| { ... } else { ... }` - unwrap `val` into `v` if it is not `none`
|
||||||
|
- multi-unwrap (see section below)
|
||||||
|
- while loops (operates on boolean conditions). examples:
|
||||||
|
- `while condition { ... }` - iterate while the condition is true
|
||||||
|
- `while condition : i += 1 { ... }` - iterate while the condition is true and execute `i += 1` (continue expression) after each iteration
|
||||||
|
- ranges (see section below)
|
||||||
|
- for loops (operates on iterable sequences). examples:
|
||||||
|
- `for items |item| { ... }` - capture just the `item` value in the array/slice (uses copy semantics, i.e. gets a `T`)
|
||||||
|
- `for items |&item| { ... }` - capture just the `item` value in the array/slice (uses (immutable) reference semantics, i.e. gets a `@T`)
|
||||||
|
- `for items |&mut item| { ... }` - capture just the `item` value in the array/slice (uses (mutable) reference semantics, i.e. gets a `@mut T`)
|
||||||
|
- `for items |item, idx| { ... }` - capture `item` and its index index in the array/slice
|
||||||
|
- `for 0..10 |i| { ... }` - iterate over the range `0..10` (exclusive)
|
||||||
|
- `for 0..=10 |i| { ... }` - iterate over the range `0..10` (inclusive)
|
||||||
|
- `for 0..(len) |i| { ... }` or equivalently `for 0..=(len - 1) |i| { ... }` - calculating range bounds, expressions must be parenthesized
|
||||||
|
- for all conditionals/guards, parentheses are optional but allowed for visual clarity
|
||||||
|
|
||||||
|
## A word on multi-unwrap
|
||||||
|
|
||||||
|
Unwrap multiple optionals with `and`. This **short-circuits**: if the first optional is none, subsequent expressions are not evaluated.
|
||||||
|
|
||||||
|
```honey
|
||||||
|
name: ?[]u8 = get_name()
|
||||||
|
age: ?u8 = get_age()
|
||||||
|
if name and age |n, a| {
|
||||||
|
# both n and a are guaranteed non-none here
|
||||||
|
print("{s} is {d} years old", {n, a})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**With guard clause on multiple values:**
|
||||||
|
|
||||||
|
```honey
|
||||||
|
if name and hat |n, h : n == "Huginn" and h.brand == .gucci| {
|
||||||
|
print("{s}'s got that drip\n", {n})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Parentheses around the expression are optional, but can aid readability when combined with guards:
|
||||||
|
|
||||||
|
```honey
|
||||||
|
# without parentheses
|
||||||
|
if name and hat |n, h : guard| { ... }
|
||||||
|
|
||||||
|
# with parentheses for clarity
|
||||||
|
if (name and hat) |n, h : guard| { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
## A word on lazy / short-circuit evaluation
|
||||||
|
|
||||||
|
The `and` in multi-unwrap short-circuits left-to-right:
|
||||||
|
|
||||||
|
```honey
|
||||||
|
if get_name() and get_hat() |n, h| {
|
||||||
|
# get_hat() is only called if get_name() returned non-none
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This is important for avoiding unnecessary computation or side effects.
|
||||||
|
|
||||||
|
## A word on ranges
|
||||||
|
|
||||||
|
Ranges represent a sequence of values, commonly used in for loops, and is itself a value type:
|
||||||
|
|
||||||
|
```
|
||||||
|
0..10 # exclusive: 0, 1, 2, ..., 9
|
||||||
|
0..=10 # inclusive: 0, 1, 2, ..., 10
|
||||||
|
```
|
||||||
|
|
||||||
|
**Parenthesization rule:** Each side of `..` must be either a simple term (literal or identifier) or a parenthesized expression. This eliminates precedence ambiguity:
|
||||||
|
|
||||||
|
```
|
||||||
|
0..10 # OK: both sides are literals
|
||||||
|
0..n # OK: both sides are simple
|
||||||
|
0..(n + 1) # OK: complex expression is parenthesized
|
||||||
|
(a + 1)..(b - 1) # OK: both sides parenthesized
|
||||||
|
# 0..n + 1 # ERROR: must parenthesize complex expressions
|
||||||
|
```
|
||||||
|
|
||||||
|
This rule keeps the grammar simple and forces clarity at the call site — no precedence rules to remember. Also, being a value type, ranges can be assigned to variables and passed around like any other value.
|
||||||
|
|||||||
@@ -134,6 +134,7 @@ Function :: struct {
|
|||||||
params: []Param,
|
params: []Param,
|
||||||
result: Type_Syntax,
|
result: Type_Syntax,
|
||||||
body: []Stmt_Id,
|
body: []Stmt_Id,
|
||||||
|
link_name: string,
|
||||||
unsupported_reason: string,
|
unsupported_reason: string,
|
||||||
diagnostic: source.Diagnostic_Id,
|
diagnostic: source.Diagnostic_Id,
|
||||||
}
|
}
|
||||||
@@ -141,10 +142,13 @@ Function :: struct {
|
|||||||
Global :: struct {
|
Global :: struct {
|
||||||
span: source.Span,
|
span: source.Span,
|
||||||
name: symbol.Id,
|
name: symbol.Id,
|
||||||
|
link_name: string,
|
||||||
pkg: Package_Id,
|
pkg: Package_Id,
|
||||||
file: File_Id,
|
file: File_Id,
|
||||||
type: Type_Syntax,
|
type: Type_Syntax,
|
||||||
immutable: bool,
|
immutable: bool,
|
||||||
|
external: bool,
|
||||||
|
writable: bool,
|
||||||
expr: Expr_Id,
|
expr: Expr_Id,
|
||||||
diagnostic: source.Diagnostic_Id,
|
diagnostic: source.Diagnostic_Id,
|
||||||
}
|
}
|
||||||
@@ -184,6 +188,16 @@ Unsupported :: struct {
|
|||||||
reason: string,
|
reason: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Trampoline is a generated C wrapper source that must be compiled and linked
|
||||||
|
// alongside the program so an internal-linkage (`static inline`) C function can
|
||||||
|
// be called through an external symbol. `source` is the wrapper function only;
|
||||||
|
// `header` is the absolute path it must `#include` (emitted once per header).
|
||||||
|
Trampoline :: struct {
|
||||||
|
symbol: string,
|
||||||
|
source: string,
|
||||||
|
header: string,
|
||||||
|
}
|
||||||
|
|
||||||
Module :: struct {
|
Module :: struct {
|
||||||
exprs: [dynamic]Expr,
|
exprs: [dynamic]Expr,
|
||||||
statements: [dynamic]Stmt,
|
statements: [dynamic]Stmt,
|
||||||
@@ -193,6 +207,7 @@ Module :: struct {
|
|||||||
files: [dynamic]File,
|
files: [dynamic]File,
|
||||||
packages: [dynamic]Package,
|
packages: [dynamic]Package,
|
||||||
unsupported: [dynamic]Unsupported,
|
unsupported: [dynamic]Unsupported,
|
||||||
|
c_trampolines: [dynamic]Trampoline,
|
||||||
strings: [dynamic]string,
|
strings: [dynamic]string,
|
||||||
type_store: types.Store,
|
type_store: types.Store,
|
||||||
allocator: mem.Allocator,
|
allocator: mem.Allocator,
|
||||||
@@ -210,6 +225,7 @@ init_module :: proc(allocator := context.allocator) -> Module {
|
|||||||
module.files.allocator = allocator
|
module.files.allocator = allocator
|
||||||
module.packages.allocator = allocator
|
module.packages.allocator = allocator
|
||||||
module.unsupported.allocator = allocator
|
module.unsupported.allocator = allocator
|
||||||
|
module.c_trampolines.allocator = allocator
|
||||||
module.strings.allocator = allocator
|
module.strings.allocator = allocator
|
||||||
return module
|
return module
|
||||||
}
|
}
|
||||||
@@ -221,17 +237,26 @@ destroy_module :: proc(module: ^Module) {
|
|||||||
for function in module.functions {
|
for function in module.functions {
|
||||||
delete(function.params, module.allocator)
|
delete(function.params, module.allocator)
|
||||||
delete(function.body, module.allocator)
|
delete(function.body, module.allocator)
|
||||||
|
delete(function.link_name, module.allocator)
|
||||||
delete(function.unsupported_reason, module.allocator)
|
delete(function.unsupported_reason, module.allocator)
|
||||||
}
|
}
|
||||||
for import_item in module.imports {
|
for import_item in module.imports {
|
||||||
delete(import_item.path, module.allocator)
|
delete(import_item.path, module.allocator)
|
||||||
}
|
}
|
||||||
|
for global in module.globals {
|
||||||
|
delete(global.link_name, module.allocator)
|
||||||
|
}
|
||||||
for pkg in module.packages {
|
for pkg in module.packages {
|
||||||
delete(pkg.path, module.allocator)
|
delete(pkg.path, module.allocator)
|
||||||
}
|
}
|
||||||
for item in module.unsupported {
|
for item in module.unsupported {
|
||||||
delete(item.reason, module.allocator)
|
delete(item.reason, module.allocator)
|
||||||
}
|
}
|
||||||
|
for trampoline in module.c_trampolines {
|
||||||
|
delete(trampoline.symbol, module.allocator)
|
||||||
|
delete(trampoline.source, module.allocator)
|
||||||
|
delete(trampoline.header, module.allocator)
|
||||||
|
}
|
||||||
for value in module.strings {
|
for value in module.strings {
|
||||||
delete(value, module.allocator)
|
delete(value, module.allocator)
|
||||||
}
|
}
|
||||||
@@ -243,6 +268,7 @@ destroy_module :: proc(module: ^Module) {
|
|||||||
delete(module.files)
|
delete(module.files)
|
||||||
delete(module.packages)
|
delete(module.packages)
|
||||||
delete(module.unsupported)
|
delete(module.unsupported)
|
||||||
|
delete(module.c_trampolines)
|
||||||
delete(module.strings)
|
delete(module.strings)
|
||||||
types.destroy_store(&module.type_store)
|
types.destroy_store(&module.type_store)
|
||||||
}
|
}
|
||||||
|
|||||||
+160
-14
@@ -84,6 +84,8 @@ Checker :: struct {
|
|||||||
global_index: []Global_Index_Entry,
|
global_index: []Global_Index_Entry,
|
||||||
import_index: []Import_Index_Entry,
|
import_index: []Import_Index_Entry,
|
||||||
global_types: []types.Type,
|
global_types: []types.Type,
|
||||||
|
external_global_canonical: []ast.Global_Id,
|
||||||
|
external_global_diagnostics: []source.Diagnostic_Id,
|
||||||
constants: []Constant,
|
constants: []Constant,
|
||||||
template_diagnostics: []source.Diagnostic_Id,
|
template_diagnostics: []source.Diagnostic_Id,
|
||||||
constant_stack: [dynamic]Constant_Frame,
|
constant_stack: [dynamic]Constant_Frame,
|
||||||
@@ -600,6 +602,72 @@ mark_expr_imports_used :: proc(checker: ^Checker, expr_id: ast.Expr_Id, file: as
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
validate_external_globals :: proc(checker: ^Checker) {
|
||||||
|
for global, global_index in checker.ast_module.globals {
|
||||||
|
checker.external_global_canonical[global_index] = ast.global_id(global_index)
|
||||||
|
if !global.external {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch global.link_name {
|
||||||
|
case "main":
|
||||||
|
checker.external_global_diagnostics[global_index] = source.add(
|
||||||
|
checker.diagnostics,
|
||||||
|
global.span,
|
||||||
|
"external C variable 'main' conflicts with the program entry point",
|
||||||
|
)
|
||||||
|
case "write":
|
||||||
|
checker.external_global_diagnostics[global_index] = source.add(
|
||||||
|
checker.diagnostics,
|
||||||
|
global.span,
|
||||||
|
"external C variable 'write' conflicts with the compiler runtime",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
for previous, previous_index in checker.ast_module.globals[:global_index] {
|
||||||
|
if !previous.external || previous.link_name != global.link_name {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
canonical := checker.external_global_canonical[previous_index]
|
||||||
|
if canonical == ast.INVALID_GLOBAL {
|
||||||
|
canonical = ast.global_id(previous_index)
|
||||||
|
}
|
||||||
|
canonical_index := int(canonical)
|
||||||
|
if canonical_index < 0 || canonical_index >= len(checker.ast_module.globals) {
|
||||||
|
canonical = ast.global_id(previous_index)
|
||||||
|
canonical_index = previous_index
|
||||||
|
}
|
||||||
|
checker.external_global_canonical[global_index] = canonical
|
||||||
|
canonical_global := checker.ast_module.globals[canonical_index]
|
||||||
|
canonical_type := checker.global_types[canonical_index]
|
||||||
|
if !types.equal(checker.global_types[global_index], canonical_type) ||
|
||||||
|
global.writable != canonical_global.writable {
|
||||||
|
checker.external_global_diagnostics[global_index] = source.addf(
|
||||||
|
checker.diagnostics,
|
||||||
|
global.span,
|
||||||
|
"conflicting external C variable declarations for '%s'",
|
||||||
|
global.link_name,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
checker.global_types[global_index] = canonical_type
|
||||||
|
break
|
||||||
|
}
|
||||||
|
for function in checker.ast_module.functions {
|
||||||
|
if !function.c_abi || function.has_body || len(function.unsupported_reason) > 0 ||
|
||||||
|
symbol_text(checker, function.name) != global.link_name {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if checker.external_global_diagnostics[global_index] == source.INVALID_DIAGNOSTIC {
|
||||||
|
checker.external_global_diagnostics[global_index] = source.addf(
|
||||||
|
checker.diagnostics,
|
||||||
|
global.span,
|
||||||
|
"external C variable '%s' conflicts with a C function declaration",
|
||||||
|
global.link_name,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
validate_declarations :: proc(checker: ^Checker) {
|
validate_declarations :: proc(checker: ^Checker) {
|
||||||
for function, function_id in checker.ast_module.functions {
|
for function, function_id in checker.ast_module.functions {
|
||||||
if len(function.unsupported_reason) > 0 {
|
if len(function.unsupported_reason) > 0 {
|
||||||
@@ -1362,6 +1430,9 @@ infer_all :: proc(checker: ^Checker) {
|
|||||||
changed := false
|
changed := false
|
||||||
spec_count := len(checker.specs)
|
spec_count := len(checker.specs)
|
||||||
for global, index in checker.ast_module.globals {
|
for global, index in checker.ast_module.globals {
|
||||||
|
if global.external {
|
||||||
|
continue
|
||||||
|
}
|
||||||
inferred := infer_expr(checker, global.expr, nil, global.pkg, global.file)
|
inferred := infer_expr(checker, global.expr, nil, global.pkg, global.file)
|
||||||
if is_runtime_type(checker, type_from_syntax(global.type)) {
|
if is_runtime_type(checker, type_from_syntax(global.type)) {
|
||||||
continue
|
continue
|
||||||
@@ -1392,6 +1463,9 @@ prune_specs :: proc(checker: ^Checker) {
|
|||||||
mark_spec_demanded(checker, find_spec(checker, main_template, nil), &stack)
|
mark_spec_demanded(checker, find_spec(checker, main_template, nil), &stack)
|
||||||
}
|
}
|
||||||
for global in checker.ast_module.globals {
|
for global in checker.ast_module.globals {
|
||||||
|
if global.external {
|
||||||
|
continue
|
||||||
|
}
|
||||||
_ = infer_expr(checker, global.expr, nil, global.pkg, global.file, &stack)
|
_ = infer_expr(checker, global.expr, nil, global.pkg, global.file, &stack)
|
||||||
}
|
}
|
||||||
for len(stack) > 0 {
|
for len(stack) > 0 {
|
||||||
@@ -1449,6 +1523,27 @@ add_unique_global :: proc(values: ^[dynamic]hir.Global_Id, value: hir.Global_Id)
|
|||||||
append(values, value)
|
append(values, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
build_global_reference :: proc(
|
||||||
|
checker: ^Checker,
|
||||||
|
global: ast.Global_Id,
|
||||||
|
span: source.Span,
|
||||||
|
global_reads: ^[dynamic]hir.Global_Id,
|
||||||
|
) -> hir.Expr_Id {
|
||||||
|
if int(global) < len(checker.external_global_diagnostics) {
|
||||||
|
diagnostic := checker.external_global_diagnostics[global]
|
||||||
|
if diagnostic != source.INVALID_DIAGNOSTIC {
|
||||||
|
return invalid_hir_expr(checker, span, diagnostic, checker.global_types[global])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hir_global := hir.Global_Id(global)
|
||||||
|
add_unique_global(global_reads, hir_global)
|
||||||
|
return add_hir_expr(checker, hir.Expr{
|
||||||
|
kind=.Global, span=span, type=checker.global_types[global],
|
||||||
|
target=hir.global_ref(hir_global), left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
add_unique_function :: proc(values: ^[dynamic]hir.Function_Id, value: hir.Function_Id) {
|
add_unique_function :: proc(values: ^[dynamic]hir.Function_Id, value: hir.Function_Id) {
|
||||||
for existing in values {
|
for existing in values {
|
||||||
if existing == value {
|
if existing == value {
|
||||||
@@ -1689,13 +1784,23 @@ hir_location_writable :: proc(checker: ^Checker, expr_id: hir.Expr_Id, locals: [
|
|||||||
return local.mutable
|
return local.mutable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case .Global:
|
||||||
|
id := hir.as_global(expr.target)
|
||||||
|
return id != hir.INVALID_GLOBAL && int(id) < len(checker.module.globals) &&
|
||||||
|
checker.module.globals[id].writable
|
||||||
case .Deref:
|
case .Deref:
|
||||||
pointer_type := checker.module.exprs[expr.left].type
|
pointer_type := checker.module.exprs[expr.left].type
|
||||||
return types.is_mutable(pointer_type, &checker.module.types)
|
return types.is_mutable(pointer_type, &checker.module.types)
|
||||||
case .Index:
|
case .Index:
|
||||||
container_type := checker.module.exprs[expr.left].type
|
container_type := checker.module.exprs[expr.left].type
|
||||||
item, ok := types.container(container_type, &checker.module.types)
|
item, ok := types.container(container_type, &checker.module.types)
|
||||||
return ok && item.mutable
|
if !ok || !item.mutable {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if types.is_array(container_type, &checker.module.types) {
|
||||||
|
return hir_location_writable(checker, expr.left, locals)
|
||||||
|
}
|
||||||
|
return true
|
||||||
case .Field:
|
case .Field:
|
||||||
base_type := checker.module.exprs[expr.left].type
|
base_type := checker.module.exprs[expr.left].type
|
||||||
if types.is_pointer(base_type, &checker.module.types) {
|
if types.is_pointer(base_type, &checker.module.types) {
|
||||||
@@ -2188,12 +2293,7 @@ build_expr :: proc(
|
|||||||
id := add_package_resolution_diagnostic(checker, expr, file)
|
id := add_package_resolution_diagnostic(checker, expr, file)
|
||||||
last = invalid_hir_expr(checker, expr.span, id)
|
last = invalid_hir_expr(checker, expr.span, id)
|
||||||
} else if global := find_global(checker, expr.name, target_pkg); global != ast.INVALID_GLOBAL {
|
} else if global := find_global(checker, expr.name, target_pkg); global != ast.INVALID_GLOBAL {
|
||||||
hir_global := hir.Global_Id(global)
|
last = build_global_reference(checker, global, expr.span, global_reads)
|
||||||
add_unique_global(global_reads, hir_global)
|
|
||||||
last = add_hir_expr(checker, hir.Expr{
|
|
||||||
kind=.Global, span=expr.span, type=checker.global_types[global],
|
|
||||||
target=hir.global_ref(hir_global), left = hir.INVALID_EXPR, right = hir.INVALID_EXPR, diagnostic = source.INVALID_DIAGNOSTIC,
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
template := find_template(checker, expr.name, target_pkg)
|
template := find_template(checker, expr.name, target_pkg)
|
||||||
if template != ast.INVALID_FUNCTION && checker.ast_module.functions[template].c_abi {
|
if template != ast.INVALID_FUNCTION && checker.ast_module.functions[template].c_abi {
|
||||||
@@ -2241,13 +2341,7 @@ build_expr :: proc(
|
|||||||
}
|
}
|
||||||
if callee == hir.INVALID_EXPR {
|
if callee == hir.INVALID_EXPR {
|
||||||
if global := find_global(checker, expr.name, target_pkg); global != ast.INVALID_GLOBAL {
|
if global := find_global(checker, expr.name, target_pkg); global != ast.INVALID_GLOBAL {
|
||||||
hir_global := hir.Global_Id(global)
|
callee = build_global_reference(checker, global, expr.span, global_reads)
|
||||||
add_unique_global(global_reads, hir_global)
|
|
||||||
callee = add_hir_expr(checker, hir.Expr{
|
|
||||||
kind=.Global, span=expr.span, type=checker.global_types[global],
|
|
||||||
target=hir.global_ref(hir_global), left=hir.INVALID_EXPR,
|
|
||||||
right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
||||||
})
|
|
||||||
callee_from_global = true
|
callee_from_global = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2561,6 +2655,9 @@ make_link_name :: proc(checker: ^Checker, id: Spec_Id) -> string {
|
|||||||
return fmt.aprintf("main", allocator = checker.allocator)
|
return fmt.aprintf("main", allocator = checker.allocator)
|
||||||
}
|
}
|
||||||
if !function.has_body && function.c_abi {
|
if !function.has_body && function.c_abi {
|
||||||
|
if len(function.link_name) > 0 {
|
||||||
|
return strings.clone(function.link_name, checker.allocator)
|
||||||
|
}
|
||||||
return fmt.aprintf("%s", symbol_text(checker, function.name), allocator = checker.allocator)
|
return fmt.aprintf("%s", symbol_text(checker, function.name), allocator = checker.allocator)
|
||||||
}
|
}
|
||||||
builder := strings.builder_make(checker.allocator)
|
builder := strings.builder_make(checker.allocator)
|
||||||
@@ -3085,6 +3182,41 @@ expr_problematic :: proc(checker: ^Checker, expr_id: hir.Expr_Id) -> bool {
|
|||||||
|
|
||||||
build_globals :: proc(checker: ^Checker) {
|
build_globals :: proc(checker: ^Checker) {
|
||||||
for global, global_index in checker.ast_module.globals {
|
for global, global_index in checker.ast_module.globals {
|
||||||
|
if global.external {
|
||||||
|
global_type := checker.global_types[global_index]
|
||||||
|
writable := global.writable
|
||||||
|
canonical := checker.external_global_canonical[global_index]
|
||||||
|
if canonical != ast.INVALID_GLOBAL && int(canonical) < len(checker.ast_module.globals) {
|
||||||
|
canonical_global := checker.ast_module.globals[canonical]
|
||||||
|
writable = canonical_global.writable
|
||||||
|
global_type = checker.global_types[canonical]
|
||||||
|
}
|
||||||
|
diagnostic := checker.external_global_diagnostics[global_index]
|
||||||
|
if !is_runtime_type(checker, global_type) {
|
||||||
|
if diagnostic == source.INVALID_DIAGNOSTIC {
|
||||||
|
diagnostic = source.addf(
|
||||||
|
checker.diagnostics,
|
||||||
|
global.span,
|
||||||
|
"could not resolve a concrete type for external global '%s'",
|
||||||
|
symbol_text(checker, global.name),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
global_type = types.I64
|
||||||
|
}
|
||||||
|
_ = hir.global_id(len(checker.module.globals))
|
||||||
|
append(&checker.module.globals, hir.Global{
|
||||||
|
name=global.name,
|
||||||
|
link_name=strings.clone(global.link_name, checker.allocator),
|
||||||
|
type=global_type,
|
||||||
|
expr=hir.INVALID_EXPR,
|
||||||
|
external=true,
|
||||||
|
writable=writable,
|
||||||
|
direct_problem=diagnostic != source.INVALID_DIAGNOSTIC,
|
||||||
|
problematic=diagnostic != source.INVALID_DIAGNOSTIC,
|
||||||
|
diagnostic=diagnostic,
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
dependencies: [dynamic]hir.Global_Id
|
dependencies: [dynamic]hir.Global_Id
|
||||||
dependencies.allocator = checker.allocator
|
dependencies.allocator = checker.allocator
|
||||||
calls: [dynamic]hir.Function_Id
|
calls: [dynamic]hir.Function_Id
|
||||||
@@ -3139,10 +3271,13 @@ build_globals :: proc(checker: ^Checker) {
|
|||||||
&checker.module.globals,
|
&checker.module.globals,
|
||||||
hir.Global {
|
hir.Global {
|
||||||
name = global.name,
|
name = global.name,
|
||||||
|
link_name = strings.clone(global.link_name, checker.allocator),
|
||||||
type = global_type,
|
type = global_type,
|
||||||
expr = expr,
|
expr = expr,
|
||||||
static_value = static_value,
|
static_value = static_value,
|
||||||
is_static = is_static,
|
is_static = is_static,
|
||||||
|
external = false,
|
||||||
|
writable = false,
|
||||||
dependencies = dependencies,
|
dependencies = dependencies,
|
||||||
calls = calls[:],
|
calls = calls[:],
|
||||||
direct_problem = expr_problematic(checker, expr),
|
direct_problem = expr_problematic(checker, expr),
|
||||||
@@ -3386,6 +3521,14 @@ check :: proc(
|
|||||||
checker.cycle_stack.allocator = allocator
|
checker.cycle_stack.allocator = allocator
|
||||||
build_symbol_indexes(&checker)
|
build_symbol_indexes(&checker)
|
||||||
checker.global_types = make([]types.Type, len(ast_module.globals), allocator)
|
checker.global_types = make([]types.Type, len(ast_module.globals), allocator)
|
||||||
|
checker.external_global_canonical = make([]ast.Global_Id, len(ast_module.globals), allocator)
|
||||||
|
checker.external_global_diagnostics = make([]source.Diagnostic_Id, len(ast_module.globals), allocator)
|
||||||
|
for &canonical in checker.external_global_canonical {
|
||||||
|
canonical = ast.INVALID_GLOBAL
|
||||||
|
}
|
||||||
|
for &diagnostic in checker.external_global_diagnostics {
|
||||||
|
diagnostic = source.INVALID_DIAGNOSTIC
|
||||||
|
}
|
||||||
checker.constants = make([]Constant, len(ast_module.exprs), allocator)
|
checker.constants = make([]Constant, len(ast_module.exprs), allocator)
|
||||||
checker.template_diagnostics = make([]source.Diagnostic_Id, len(ast_module.functions), allocator)
|
checker.template_diagnostics = make([]source.Diagnostic_Id, len(ast_module.functions), allocator)
|
||||||
for &diagnostic in checker.template_diagnostics {
|
for &diagnostic in checker.template_diagnostics {
|
||||||
@@ -3400,6 +3543,8 @@ check :: proc(
|
|||||||
delete(checker.global_index, allocator)
|
delete(checker.global_index, allocator)
|
||||||
delete(checker.import_index, allocator)
|
delete(checker.import_index, allocator)
|
||||||
delete(checker.global_types, allocator)
|
delete(checker.global_types, allocator)
|
||||||
|
delete(checker.external_global_canonical, allocator)
|
||||||
|
delete(checker.external_global_diagnostics, allocator)
|
||||||
delete(checker.constants, allocator)
|
delete(checker.constants, allocator)
|
||||||
delete(checker.template_diagnostics, allocator)
|
delete(checker.template_diagnostics, allocator)
|
||||||
delete(checker.constant_stack)
|
delete(checker.constant_stack)
|
||||||
@@ -3433,6 +3578,7 @@ check :: proc(
|
|||||||
validate_type_nodes(&checker)
|
validate_type_nodes(&checker)
|
||||||
validate_declarations(&checker)
|
validate_declarations(&checker)
|
||||||
infer_all(&checker)
|
infer_all(&checker)
|
||||||
|
validate_external_globals(&checker)
|
||||||
prune_specs(&checker)
|
prune_specs(&checker)
|
||||||
build_globals(&checker)
|
build_globals(&checker)
|
||||||
for index := 0; index < len(checker.specs); index += 1 {
|
for index := 0; index < len(checker.specs); index += 1 {
|
||||||
|
|||||||
@@ -72,12 +72,55 @@ Function :: struct {
|
|||||||
params: []Type_Id,
|
params: []Type_Id,
|
||||||
result: Type_Id,
|
result: Type_Id,
|
||||||
variadic: bool,
|
variadic: bool,
|
||||||
|
link_name: string,
|
||||||
|
reason: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trampoline is a generated C wrapper that gives an external symbol to a
|
||||||
|
// `static inline` (or otherwise internal-linkage) C function so brolang can
|
||||||
|
// call it. The wrapper is compiled and linked alongside the program. `source`
|
||||||
|
// is the wrapper function only; `header` is the absolute path the wrapper must
|
||||||
|
// `#include`, emitted once per unique header by the driver.
|
||||||
|
Trampoline :: struct {
|
||||||
|
symbol: string,
|
||||||
|
source: string,
|
||||||
|
header: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
Variable :: struct {
|
||||||
|
name: string,
|
||||||
|
type: Type_Id,
|
||||||
|
mutable: bool,
|
||||||
|
reason: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
Macro_Value_Kind :: enum u8 {
|
||||||
|
Invalid,
|
||||||
|
Integer,
|
||||||
|
Float,
|
||||||
|
}
|
||||||
|
|
||||||
|
Macro_Value :: struct {
|
||||||
|
kind: Macro_Value_Kind,
|
||||||
|
type: Type_Id,
|
||||||
|
integer: u64,
|
||||||
|
negative: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
Macro_Constant :: struct {
|
||||||
|
name: string,
|
||||||
|
type: Type_Id,
|
||||||
|
type_name: string,
|
||||||
|
value: Macro_Value,
|
||||||
|
values: []Macro_Value,
|
||||||
|
aggregate: bool,
|
||||||
reason: string,
|
reason: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
Unsupported :: struct {
|
Unsupported :: struct {
|
||||||
name: string,
|
name: string,
|
||||||
reason: string,
|
reason: string,
|
||||||
|
final_macro: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
Result :: struct {
|
Result :: struct {
|
||||||
@@ -85,7 +128,10 @@ Result :: struct {
|
|||||||
records: [dynamic]Record,
|
records: [dynamic]Record,
|
||||||
aliases: [dynamic]Alias,
|
aliases: [dynamic]Alias,
|
||||||
functions: [dynamic]Function,
|
functions: [dynamic]Function,
|
||||||
|
variables: [dynamic]Variable,
|
||||||
|
macros: [dynamic]Macro_Constant,
|
||||||
unsupported: [dynamic]Unsupported,
|
unsupported: [dynamic]Unsupported,
|
||||||
|
trampolines: [dynamic]Trampoline,
|
||||||
error_message: string,
|
error_message: string,
|
||||||
infrastructure: bool,
|
infrastructure: bool,
|
||||||
available: bool,
|
available: bool,
|
||||||
@@ -99,7 +145,10 @@ init_result :: proc(allocator := context.allocator) -> Result {
|
|||||||
result.records.allocator = allocator
|
result.records.allocator = allocator
|
||||||
result.aliases.allocator = allocator
|
result.aliases.allocator = allocator
|
||||||
result.functions.allocator = allocator
|
result.functions.allocator = allocator
|
||||||
|
result.variables.allocator = allocator
|
||||||
|
result.macros.allocator = allocator
|
||||||
result.unsupported.allocator = allocator
|
result.unsupported.allocator = allocator
|
||||||
|
result.trampolines.allocator = allocator
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,18 +172,37 @@ destroy_result :: proc(result: ^Result) {
|
|||||||
for function in result.functions {
|
for function in result.functions {
|
||||||
delete(function.name, result.allocator)
|
delete(function.name, result.allocator)
|
||||||
delete(function.params, result.allocator)
|
delete(function.params, result.allocator)
|
||||||
|
delete(function.link_name, result.allocator)
|
||||||
delete(function.reason, result.allocator)
|
delete(function.reason, result.allocator)
|
||||||
}
|
}
|
||||||
|
for variable in result.variables {
|
||||||
|
delete(variable.name, result.allocator)
|
||||||
|
delete(variable.reason, result.allocator)
|
||||||
|
}
|
||||||
|
for macro in result.macros {
|
||||||
|
delete(macro.name, result.allocator)
|
||||||
|
delete(macro.type_name, result.allocator)
|
||||||
|
delete(macro.values, result.allocator)
|
||||||
|
delete(macro.reason, result.allocator)
|
||||||
|
}
|
||||||
for item in result.unsupported {
|
for item in result.unsupported {
|
||||||
delete(item.name, result.allocator)
|
delete(item.name, result.allocator)
|
||||||
delete(item.reason, result.allocator)
|
delete(item.reason, result.allocator)
|
||||||
}
|
}
|
||||||
|
for trampoline in result.trampolines {
|
||||||
|
delete(trampoline.symbol, result.allocator)
|
||||||
|
delete(trampoline.source, result.allocator)
|
||||||
|
delete(trampoline.header, result.allocator)
|
||||||
|
}
|
||||||
delete(result.error_message, result.allocator)
|
delete(result.error_message, result.allocator)
|
||||||
delete(result.types)
|
delete(result.types)
|
||||||
delete(result.records)
|
delete(result.records)
|
||||||
delete(result.aliases)
|
delete(result.aliases)
|
||||||
delete(result.functions)
|
delete(result.functions)
|
||||||
|
delete(result.variables)
|
||||||
|
delete(result.macros)
|
||||||
delete(result.unsupported)
|
delete(result.unsupported)
|
||||||
|
delete(result.trampolines)
|
||||||
}
|
}
|
||||||
|
|
||||||
Request :: struct {
|
Request :: struct {
|
||||||
|
|||||||
+905
-21
File diff suppressed because it is too large
Load Diff
+61
-1
@@ -15,6 +15,18 @@ import "core:fmt"
|
|||||||
import vmem "core:mem/virtual"
|
import vmem "core:mem/virtual"
|
||||||
import "core:os"
|
import "core:os"
|
||||||
import "core:os/os2"
|
import "core:os/os2"
|
||||||
|
import "core:strings"
|
||||||
|
|
||||||
|
// write_escaped_c_string writes value into builder with `\` and `"` escaped so
|
||||||
|
// it is safe to embed inside a C string literal (e.g. an `#include "..."`).
|
||||||
|
write_escaped_c_string :: proc(builder: ^strings.Builder, value: string) {
|
||||||
|
for b in transmute([]byte)value {
|
||||||
|
if b == '\\' || b == '"' {
|
||||||
|
strings.write_byte(builder, '\\')
|
||||||
|
}
|
||||||
|
strings.write_byte(builder, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
compile_package :: proc(
|
compile_package :: proc(
|
||||||
input_path, output_path: string,
|
input_path, output_path: string,
|
||||||
@@ -69,6 +81,54 @@ compile_package :: proc(
|
|||||||
fmt.eprintln("failed to load root package directory:", input_path)
|
fmt.eprintln("failed to load root package directory:", input_path)
|
||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generated C trampolines (for `static inline` imports) must be compiled and
|
||||||
|
// linked with the program. Write them out and add the source as a link input
|
||||||
|
// before the loader's arena (which owns the trampoline strings) is freed.
|
||||||
|
trampoline_path := ""
|
||||||
|
effective_link_arguments := link_arguments
|
||||||
|
owns_arguments := false
|
||||||
|
// Register the path defer first so it runs last (LIFO): the augmented
|
||||||
|
// argument slice, whose Input value aliases trampoline_path, is freed before
|
||||||
|
// the path string it points at.
|
||||||
|
defer if len(trampoline_path) > 0 {
|
||||||
|
_ = os.remove(trampoline_path)
|
||||||
|
delete(trampoline_path)
|
||||||
|
}
|
||||||
|
defer if owns_arguments {
|
||||||
|
delete(effective_link_arguments)
|
||||||
|
}
|
||||||
|
if len(ast_module.c_trampolines) > 0 {
|
||||||
|
builder := strings.builder_make()
|
||||||
|
defer strings.builder_destroy(&builder)
|
||||||
|
seen_headers: map[string]bool
|
||||||
|
defer delete(seen_headers)
|
||||||
|
for trampoline in ast_module.c_trampolines {
|
||||||
|
if seen_headers[trampoline.header] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen_headers[trampoline.header] = true
|
||||||
|
strings.write_string(&builder, "#include \"")
|
||||||
|
write_escaped_c_string(&builder, trampoline.header)
|
||||||
|
strings.write_string(&builder, "\"\n")
|
||||||
|
}
|
||||||
|
for trampoline in ast_module.c_trampolines {
|
||||||
|
strings.write_string(&builder, trampoline.source)
|
||||||
|
}
|
||||||
|
// Owned (stable allocator); freed in the path defer above after backend
|
||||||
|
// compilation, which is the only consumer, has run.
|
||||||
|
trampoline_path = fmt.aprintf("%s.brolang-trampolines-%d.c", output_path, os2.get_pid())
|
||||||
|
if err := os.write_entire_file_or_err(trampoline_path, transmute([]byte)strings.to_string(builder)); err != nil {
|
||||||
|
fmt.eprintln("failed to write C trampoline source:", err)
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
augmented := make([]linker.Argument, len(link_arguments)+1)
|
||||||
|
copy(augmented, link_arguments)
|
||||||
|
augmented[len(link_arguments)] = linker.Argument{kind=.Input, value=trampoline_path}
|
||||||
|
effective_link_arguments = augmented
|
||||||
|
owns_arguments = true
|
||||||
|
}
|
||||||
|
|
||||||
vmem.arena_free_all(&lexer_arena)
|
vmem.arena_free_all(&lexer_arena)
|
||||||
hir_module := checker.check(&ast_module, &diagnostics, &symbols, selected, vmem.arena_allocator(&checker_arena))
|
hir_module := checker.check(&ast_module, &diagnostics, &symbols, selected, vmem.arena_allocator(&checker_arena))
|
||||||
vmem.arena_free_all(&parser_arena)
|
vmem.arena_free_all(&parser_arena)
|
||||||
@@ -87,7 +147,7 @@ compile_package :: proc(
|
|||||||
}
|
}
|
||||||
|
|
||||||
source.print_all(&diagnostics)
|
source.print_all(&diagnostics)
|
||||||
if !backend.compile(llvm_path, output_path, link_arguments, selected, c_options) {
|
if !backend.compile(llvm_path, output_path, effective_link_arguments, selected, c_options) {
|
||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
if len(diagnostics.items) > 0 {
|
if len(diagnostics.items) > 0 {
|
||||||
|
|||||||
@@ -161,10 +161,13 @@ Function :: struct {
|
|||||||
|
|
||||||
Global :: struct {
|
Global :: struct {
|
||||||
name: symbol.Id,
|
name: symbol.Id,
|
||||||
|
link_name: string,
|
||||||
type: types.Type,
|
type: types.Type,
|
||||||
expr: Expr_Id,
|
expr: Expr_Id,
|
||||||
static_value: i64,
|
static_value: i64,
|
||||||
is_static: bool,
|
is_static: bool,
|
||||||
|
external: bool,
|
||||||
|
writable: bool,
|
||||||
dependencies: [dynamic]Global_Id,
|
dependencies: [dynamic]Global_Id,
|
||||||
calls: []Function_Id,
|
calls: []Function_Id,
|
||||||
direct_problem: bool,
|
direct_problem: bool,
|
||||||
@@ -210,6 +213,7 @@ destroy_module :: proc(module: ^Module) {
|
|||||||
delete(function.calls, module.allocator)
|
delete(function.calls, module.allocator)
|
||||||
}
|
}
|
||||||
for global in module.globals {
|
for global in module.globals {
|
||||||
|
delete(global.link_name, module.allocator)
|
||||||
delete(global.dependencies)
|
delete(global.dependencies)
|
||||||
delete(global.calls, module.allocator)
|
delete(global.calls, module.allocator)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,8 +128,11 @@ Function :: struct {
|
|||||||
|
|
||||||
Global :: struct {
|
Global :: struct {
|
||||||
name: symbol.Id,
|
name: symbol.Id,
|
||||||
|
link_name: string,
|
||||||
type: types.Type,
|
type: types.Type,
|
||||||
is_static: bool,
|
is_static: bool,
|
||||||
|
external: bool,
|
||||||
|
writable: bool,
|
||||||
static_value: i64,
|
static_value: i64,
|
||||||
initializer: []Instruction,
|
initializer: []Instruction,
|
||||||
problematic: bool,
|
problematic: bool,
|
||||||
@@ -171,6 +174,7 @@ destroy_module :: proc(module: ^Module) {
|
|||||||
destroy_instructions(function.instructions, module.allocator)
|
destroy_instructions(function.instructions, module.allocator)
|
||||||
}
|
}
|
||||||
for global in module.globals {
|
for global in module.globals {
|
||||||
|
delete(global.link_name, module.allocator)
|
||||||
destroy_instructions(global.initializer, module.allocator)
|
destroy_instructions(global.initializer, module.allocator)
|
||||||
}
|
}
|
||||||
for value in module.strings {
|
for value in module.strings {
|
||||||
|
|||||||
+43
-5
@@ -600,7 +600,15 @@ emit_instruction_stream :: proc(
|
|||||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid global reference type")
|
emit_recovery_value(emitter, instruction_index, instruction, "invalid global reference type")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if global.is_static {
|
if global.external {
|
||||||
|
fmt.sbprintf(
|
||||||
|
&emitter.builder,
|
||||||
|
" %%v%d = load %s, ptr @%s\n",
|
||||||
|
instruction_index,
|
||||||
|
llvm_type(global.type, &emitter.module.types),
|
||||||
|
global.link_name,
|
||||||
|
)
|
||||||
|
} else if global.is_static {
|
||||||
fmt.sbprintf(
|
fmt.sbprintf(
|
||||||
&emitter.builder,
|
&emitter.builder,
|
||||||
" %%v%d = load %s, ptr @bro.g.%d\n",
|
" %%v%d = load %s, ptr @bro.g.%d\n",
|
||||||
@@ -632,6 +640,15 @@ emit_instruction_stream :: proc(
|
|||||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid global address")
|
emit_recovery_value(emitter, instruction_index, instruction, "invalid global address")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
global := emitter.module.globals[global_id]
|
||||||
|
if global.external {
|
||||||
|
fmt.sbprintf(
|
||||||
|
&emitter.builder,
|
||||||
|
" %%v%d = getelementptr %s, ptr @%s, i64 0\n",
|
||||||
|
instruction_index, llvm_type(instruction.type, &emitter.module.types), global.link_name,
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
}
|
||||||
fmt.sbprintf(
|
fmt.sbprintf(
|
||||||
&emitter.builder,
|
&emitter.builder,
|
||||||
" %%v%d = getelementptr %s, ptr @bro.g.%d, i64 0\n",
|
" %%v%d = getelementptr %s, ptr @bro.g.%d, i64 0\n",
|
||||||
@@ -1369,7 +1386,28 @@ emit_instruction_stream :: proc(
|
|||||||
|
|
||||||
emit_globals :: proc(emitter: ^Emitter) {
|
emit_globals :: proc(emitter: ^Emitter) {
|
||||||
for global, global_id in emitter.module.globals {
|
for global, global_id in emitter.module.globals {
|
||||||
if global.is_static {
|
if global.external {
|
||||||
|
if global.problematic {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
duplicate := false
|
||||||
|
for previous in emitter.module.globals[:global_id] {
|
||||||
|
if previous.external && !previous.problematic && previous.link_name == global.link_name {
|
||||||
|
duplicate = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if duplicate {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fmt.sbprintf(
|
||||||
|
&emitter.builder,
|
||||||
|
"@%s = external %s %s\n",
|
||||||
|
global.link_name,
|
||||||
|
"global" if global.writable else "constant",
|
||||||
|
llvm_type(global.type, &emitter.module.types),
|
||||||
|
)
|
||||||
|
} else if global.is_static {
|
||||||
fmt.sbprintf(
|
fmt.sbprintf(
|
||||||
&emitter.builder,
|
&emitter.builder,
|
||||||
"@bro.g.%d = internal constant %s ",
|
"@bro.g.%d = internal constant %s ",
|
||||||
@@ -1462,7 +1500,7 @@ emit_strings :: proc(emitter: ^Emitter) {
|
|||||||
emit_global_accessors :: proc(emitter: ^Emitter) {
|
emit_global_accessors :: proc(emitter: ^Emitter) {
|
||||||
placeholder_function := ir.Function{result=types.I64}
|
placeholder_function := ir.Function{result=types.I64}
|
||||||
for global, global_id in emitter.module.globals {
|
for global, global_id in emitter.module.globals {
|
||||||
if global.is_static {
|
if global.is_static || global.external {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
type_name := llvm_type(global.type, &emitter.module.types)
|
type_name := llvm_type(global.type, &emitter.module.types)
|
||||||
@@ -1500,7 +1538,7 @@ emit_global_accessors :: proc(emitter: ^Emitter) {
|
|||||||
emit_constructor :: proc(emitter: ^Emitter) {
|
emit_constructor :: proc(emitter: ^Emitter) {
|
||||||
count := 0
|
count := 0
|
||||||
for global in emitter.module.globals {
|
for global in emitter.module.globals {
|
||||||
if !global.is_static && !global.problematic {
|
if !global.is_static && !global.external && !global.problematic {
|
||||||
count += 1
|
count += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1513,7 +1551,7 @@ emit_constructor :: proc(emitter: ^Emitter) {
|
|||||||
)
|
)
|
||||||
strings.write_string(&emitter.builder, "define internal void @bro.init() {\nentry:\n")
|
strings.write_string(&emitter.builder, "define internal void @bro.init() {\nentry:\n")
|
||||||
for global, global_id in emitter.module.globals {
|
for global, global_id in emitter.module.globals {
|
||||||
if !global.is_static && !global.problematic {
|
if !global.is_static && !global.external && !global.problematic {
|
||||||
fmt.sbprintf(&emitter.builder, " %%g%d = call %s @bro.get.%d()\n", global_id, llvm_type(global.type, &emitter.module.types), global_id)
|
fmt.sbprintf(&emitter.builder, " %%g%d = call %s @bro.get.%d()\n", global_id, llvm_type(global.type, &emitter.module.types), global_id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+645
-1
@@ -9,6 +9,7 @@ import "../symbol"
|
|||||||
import "../target"
|
import "../target"
|
||||||
import "../types"
|
import "../types"
|
||||||
import "core:fmt"
|
import "core:fmt"
|
||||||
|
import "core:math"
|
||||||
import "core:mem"
|
import "core:mem"
|
||||||
import "core:os"
|
import "core:os"
|
||||||
import "core:path/filepath"
|
import "core:path/filepath"
|
||||||
@@ -296,6 +297,625 @@ c_record_by_value_reason :: proc(result: ^cimport.Result, value: cimport.Type_Id
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
add_import_unsupported :: proc(state: ^State, pkg: ast.Package_Id, name: string, reason: string) {
|
||||||
|
if len(name) == 0 || len(reason) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
append(&state.module.unsupported, ast.Unsupported{
|
||||||
|
pkg=pkg,
|
||||||
|
name=symbol.intern(state.symbols, name),
|
||||||
|
reason=strings.clone(reason, state.allocator),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
find_trampoline :: proc(result: ^cimport.Result, symbol: string) -> (cimport.Trampoline, bool) {
|
||||||
|
for trampoline in result.trampolines {
|
||||||
|
if trampoline.symbol == symbol {
|
||||||
|
return trampoline, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
add_c_trampoline :: proc(state: ^State, trampoline: cimport.Trampoline) {
|
||||||
|
if len(trampoline.symbol) == 0 || len(trampoline.source) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for existing in state.module.c_trampolines {
|
||||||
|
if existing.symbol == trampoline.symbol {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
append(&state.module.c_trampolines, ast.Trampoline{
|
||||||
|
symbol=strings.clone(trampoline.symbol, state.allocator),
|
||||||
|
source=strings.clone(trampoline.source, state.allocator),
|
||||||
|
header=strings.clone(trampoline.header, state.allocator),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
add_import_expr :: proc(state: ^State, expr: ast.Expr) -> ast.Expr_Id {
|
||||||
|
id := ast.expr_id(len(state.module.exprs))
|
||||||
|
append(&state.module.exprs, expr)
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
add_macro_value_expr :: proc(state: ^State, value: cimport.Macro_Value, span: source.Span) -> ast.Expr_Id {
|
||||||
|
#partial switch value.kind {
|
||||||
|
case .Integer:
|
||||||
|
if value.negative {
|
||||||
|
magnitude := value.integer
|
||||||
|
operand := add_import_expr(state, ast.Expr{
|
||||||
|
kind=.Integer, span=span, integer=magnitude,
|
||||||
|
left=ast.INVALID_EXPR, right=ast.INVALID_EXPR,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
return add_import_expr(state, ast.Expr{
|
||||||
|
kind=.Negate, span=span, left=operand, right=ast.INVALID_EXPR,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return add_import_expr(state, ast.Expr{
|
||||||
|
kind=.Integer, span=span, integer=value.integer,
|
||||||
|
left=ast.INVALID_EXPR, right=ast.INVALID_EXPR,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
case .Float:
|
||||||
|
return add_import_expr(state, ast.Expr{
|
||||||
|
kind=.Float, span=span, integer=value.integer,
|
||||||
|
left=ast.INVALID_EXPR, right=ast.INVALID_EXPR,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
case:
|
||||||
|
}
|
||||||
|
return add_import_expr(state, ast.Expr{
|
||||||
|
kind=.Invalid, span=span, left=ast.INVALID_EXPR, right=ast.INVALID_EXPR,
|
||||||
|
diagnostic=source.add(state.diagnostics, span, "unsupported C macro value"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
MAX_MACRO_ZERO_DEPTH :: 64
|
||||||
|
MAX_MACRO_ZERO_NODES :: 65_536
|
||||||
|
|
||||||
|
find_macro_record_name :: proc(
|
||||||
|
state: ^State,
|
||||||
|
pkg: ast.Package_Id,
|
||||||
|
value: types.Type,
|
||||||
|
) -> (symbol.Id, bool) {
|
||||||
|
resolved := types.resolve_alias(value, &state.module.type_store)
|
||||||
|
for item, index in state.module.type_store.nodes {
|
||||||
|
if item.pkg != u32(pkg) || item.name == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
candidate := types.DYNAMIC_START+types.Type(index)
|
||||||
|
if types.resolve_alias(candidate, &state.module.type_store) == resolved {
|
||||||
|
return symbol.Id(item.name), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return symbol.INVALID, false
|
||||||
|
}
|
||||||
|
|
||||||
|
add_macro_zero_expr :: proc(
|
||||||
|
state: ^State,
|
||||||
|
pkg: ast.Package_Id,
|
||||||
|
value_type: types.Type,
|
||||||
|
span: source.Span,
|
||||||
|
depth: int,
|
||||||
|
remaining: ^int,
|
||||||
|
) -> (ast.Expr_Id, bool) {
|
||||||
|
if depth > MAX_MACRO_ZERO_DEPTH || remaining^ <= 0 {
|
||||||
|
return ast.INVALID_EXPR, false
|
||||||
|
}
|
||||||
|
remaining^ -= 1
|
||||||
|
store := &state.module.type_store
|
||||||
|
resolved := types.resolve_alias(value_type, store)
|
||||||
|
#partial switch types.kind(resolved, store) {
|
||||||
|
case .Scalar:
|
||||||
|
kind := ast.Expr_Kind.Integer
|
||||||
|
if types.is_float(resolved, state.selected) {
|
||||||
|
kind = .Float
|
||||||
|
}
|
||||||
|
return add_import_expr(state, ast.Expr{
|
||||||
|
kind=kind, span=span, integer=0,
|
||||||
|
left=ast.INVALID_EXPR, right=ast.INVALID_EXPR,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
}), true
|
||||||
|
case .Optional:
|
||||||
|
if !types.is_optional_pointer(resolved, store) {
|
||||||
|
return ast.INVALID_EXPR, false
|
||||||
|
}
|
||||||
|
return add_import_expr(state, ast.Expr{
|
||||||
|
kind=.None, span=span,
|
||||||
|
left=ast.INVALID_EXPR, right=ast.INVALID_EXPR,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
}), true
|
||||||
|
case .Array:
|
||||||
|
item, ok := types.node(store, resolved)
|
||||||
|
if !ok || item.count > u64(max(int)) || item.count > u64(remaining^) {
|
||||||
|
return ast.INVALID_EXPR, false
|
||||||
|
}
|
||||||
|
args := make([]ast.Expr_Id, int(item.count), state.allocator)
|
||||||
|
for index in 0..<len(args) {
|
||||||
|
value, value_ok := add_macro_zero_expr(
|
||||||
|
state, pkg, item.child, span, depth+1, remaining,
|
||||||
|
)
|
||||||
|
if !value_ok {
|
||||||
|
delete(args, state.allocator)
|
||||||
|
return ast.INVALID_EXPR, false
|
||||||
|
}
|
||||||
|
args[index] = value
|
||||||
|
}
|
||||||
|
return add_import_expr(state, ast.Expr{
|
||||||
|
kind=.Array, span=span, args=args,
|
||||||
|
left=ast.INVALID_EXPR, right=ast.INVALID_EXPR,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
}), true
|
||||||
|
case .Struct, .Union:
|
||||||
|
item, ok := types.node(store, resolved)
|
||||||
|
fields := types.fields_for(store, resolved)
|
||||||
|
if !ok || len(fields) == 0 {
|
||||||
|
return ast.INVALID_EXPR, false
|
||||||
|
}
|
||||||
|
name, found_name := find_macro_record_name(state, pkg, resolved)
|
||||||
|
if !found_name {
|
||||||
|
return ast.INVALID_EXPR, false
|
||||||
|
}
|
||||||
|
count := 1 if item.kind == .Union else len(fields)
|
||||||
|
args := make([]ast.Expr_Id, count, state.allocator)
|
||||||
|
for index in 0..<count {
|
||||||
|
field := fields[index]
|
||||||
|
value, value_ok := add_macro_zero_expr(
|
||||||
|
state, pkg, field.type, span, depth+1, remaining,
|
||||||
|
)
|
||||||
|
if !value_ok {
|
||||||
|
delete(args, state.allocator)
|
||||||
|
return ast.INVALID_EXPR, false
|
||||||
|
}
|
||||||
|
args[index] = add_import_expr(state, ast.Expr{
|
||||||
|
kind=.Keyed, span=span, name=symbol.Id(field.name), left=value,
|
||||||
|
right=ast.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return add_import_expr(state, ast.Expr{
|
||||||
|
kind=.Struct_Literal, span=span, name=name, args=args,
|
||||||
|
left=ast.INVALID_EXPR, right=ast.INVALID_EXPR,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
}), true
|
||||||
|
case:
|
||||||
|
}
|
||||||
|
return ast.INVALID_EXPR, false
|
||||||
|
}
|
||||||
|
|
||||||
|
Converted_Macro_Value_Kind :: enum u8 {
|
||||||
|
Invalid,
|
||||||
|
Integer,
|
||||||
|
Float,
|
||||||
|
Null,
|
||||||
|
}
|
||||||
|
|
||||||
|
Converted_Macro_Value :: struct {
|
||||||
|
kind: Converted_Macro_Value_Kind,
|
||||||
|
integer: u64,
|
||||||
|
negative: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_integer_value :: proc(value: cimport.Macro_Value) -> i128 {
|
||||||
|
magnitude := i128(value.integer)
|
||||||
|
return -magnitude if value.negative else magnitude
|
||||||
|
}
|
||||||
|
|
||||||
|
convert_macro_integer :: proc(
|
||||||
|
value: i128,
|
||||||
|
destination: types.Type,
|
||||||
|
selected: target.Target,
|
||||||
|
) -> (Converted_Macro_Value, bool) {
|
||||||
|
bits := types.bits(destination, selected)
|
||||||
|
if bits <= 0 || bits > 64 {
|
||||||
|
return {}, false
|
||||||
|
}
|
||||||
|
if types.is_unsigned(destination, selected) {
|
||||||
|
modulus := i128(1) << u32(bits)
|
||||||
|
wrapped := value % modulus
|
||||||
|
if wrapped < 0 {
|
||||||
|
wrapped += modulus
|
||||||
|
}
|
||||||
|
return Converted_Macro_Value{
|
||||||
|
kind=.Integer,
|
||||||
|
integer=u64(wrapped),
|
||||||
|
}, true
|
||||||
|
}
|
||||||
|
if !types.is_signed(destination, selected) {
|
||||||
|
return {}, false
|
||||||
|
}
|
||||||
|
limit := i128(1) << u32(bits-1)
|
||||||
|
if value < -limit || value >= limit {
|
||||||
|
return {}, false
|
||||||
|
}
|
||||||
|
if value < 0 {
|
||||||
|
return Converted_Macro_Value{
|
||||||
|
kind=.Integer,
|
||||||
|
integer=u64(-value),
|
||||||
|
negative=true,
|
||||||
|
}, true
|
||||||
|
}
|
||||||
|
return Converted_Macro_Value{kind=.Integer, integer=u64(value)}, true
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_float_value :: proc(
|
||||||
|
value: cimport.Macro_Value,
|
||||||
|
source_type: types.Type,
|
||||||
|
selected: target.Target,
|
||||||
|
) -> (f64, bool) {
|
||||||
|
if value.kind != .Float || !types.is_float(source_type, selected) {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
number := transmute(f64)value.integer
|
||||||
|
if types.bits(source_type, selected) == 32 {
|
||||||
|
number = f64(f32(number))
|
||||||
|
}
|
||||||
|
if math.is_nan(number) || math.is_inf(number) {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
return number, true
|
||||||
|
}
|
||||||
|
|
||||||
|
convert_macro_float :: proc(
|
||||||
|
number: f64,
|
||||||
|
destination: types.Type,
|
||||||
|
selected: target.Target,
|
||||||
|
) -> (Converted_Macro_Value, bool) {
|
||||||
|
if !types.is_float(destination, selected) {
|
||||||
|
return {}, false
|
||||||
|
}
|
||||||
|
converted := number
|
||||||
|
if types.bits(destination, selected) == 32 {
|
||||||
|
rounded := f64(f32(number))
|
||||||
|
if math.is_inf(rounded) {
|
||||||
|
return {}, false
|
||||||
|
}
|
||||||
|
converted = rounded
|
||||||
|
}
|
||||||
|
return Converted_Macro_Value{
|
||||||
|
kind=.Float,
|
||||||
|
integer=transmute(u64)converted,
|
||||||
|
}, true
|
||||||
|
}
|
||||||
|
|
||||||
|
convert_macro_field_value :: proc(
|
||||||
|
state: ^State,
|
||||||
|
result: ^cimport.Result,
|
||||||
|
value: cimport.Macro_Value,
|
||||||
|
field_type: types.Type,
|
||||||
|
pkg: ast.Package_Id,
|
||||||
|
record_mapping: []types.Type,
|
||||||
|
type_mapping: []types.Type,
|
||||||
|
) -> (Converted_Macro_Value, bool) {
|
||||||
|
store := &state.module.type_store
|
||||||
|
destination := types.resolve_alias(field_type, store)
|
||||||
|
source_type := translate_c_type(
|
||||||
|
state, result, value.type, pkg, record_mapping, type_mapping,
|
||||||
|
)
|
||||||
|
if !types.is_concrete_scalar(source_type) {
|
||||||
|
return {}, false
|
||||||
|
}
|
||||||
|
if types.is_optional_pointer(destination, store) {
|
||||||
|
if types.is_concrete_integer(source_type) &&
|
||||||
|
value.kind == .Integer && macro_integer_value(value) == 0 {
|
||||||
|
return Converted_Macro_Value{kind=.Null}, true
|
||||||
|
}
|
||||||
|
return {}, false
|
||||||
|
}
|
||||||
|
if types.is_concrete_integer(source_type) {
|
||||||
|
if value.kind != .Integer {
|
||||||
|
return {}, false
|
||||||
|
}
|
||||||
|
integer := macro_integer_value(value)
|
||||||
|
if types.is_concrete_integer(destination) {
|
||||||
|
return convert_macro_integer(integer, destination, state.selected)
|
||||||
|
}
|
||||||
|
if types.is_float(destination, state.selected) {
|
||||||
|
return convert_macro_float(f64(integer), destination, state.selected)
|
||||||
|
}
|
||||||
|
return {}, false
|
||||||
|
}
|
||||||
|
number, number_ok := macro_float_value(value, source_type, state.selected)
|
||||||
|
if !number_ok {
|
||||||
|
return {}, false
|
||||||
|
}
|
||||||
|
if types.is_float(destination, state.selected) {
|
||||||
|
return convert_macro_float(number, destination, state.selected)
|
||||||
|
}
|
||||||
|
if types.is_concrete_integer(destination) {
|
||||||
|
truncated := math.trunc(number)
|
||||||
|
bits := types.bits(destination, state.selected)
|
||||||
|
if bits <= 0 || bits > 64 {
|
||||||
|
return {}, false
|
||||||
|
}
|
||||||
|
if types.is_signed(destination, state.selected) {
|
||||||
|
limit := f64(i128(1) << u32(bits-1))
|
||||||
|
if truncated < -limit || truncated >= limit {
|
||||||
|
return {}, false
|
||||||
|
}
|
||||||
|
} else if types.is_unsigned(destination, state.selected) {
|
||||||
|
limit := f64(i128(1) << u32(bits))
|
||||||
|
if truncated < 0 || truncated >= limit {
|
||||||
|
return {}, false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {}, false
|
||||||
|
}
|
||||||
|
return convert_macro_integer(i128(truncated), destination, state.selected)
|
||||||
|
}
|
||||||
|
return {}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
add_converted_macro_value_expr :: proc(
|
||||||
|
state: ^State,
|
||||||
|
value: Converted_Macro_Value,
|
||||||
|
span: source.Span,
|
||||||
|
) -> (ast.Expr_Id, bool) {
|
||||||
|
#partial switch value.kind {
|
||||||
|
case .Integer:
|
||||||
|
return add_macro_value_expr(state, cimport.Macro_Value{
|
||||||
|
kind=.Integer,
|
||||||
|
integer=value.integer,
|
||||||
|
negative=value.negative,
|
||||||
|
}, span), true
|
||||||
|
case .Float:
|
||||||
|
return add_macro_value_expr(state, cimport.Macro_Value{
|
||||||
|
kind=.Float,
|
||||||
|
integer=value.integer,
|
||||||
|
}, span), true
|
||||||
|
case .Null:
|
||||||
|
return add_import_expr(state, ast.Expr{
|
||||||
|
kind=.None, span=span,
|
||||||
|
left=ast.INVALID_EXPR, right=ast.INVALID_EXPR,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
}), true
|
||||||
|
case:
|
||||||
|
}
|
||||||
|
return ast.INVALID_EXPR, false
|
||||||
|
}
|
||||||
|
|
||||||
|
add_macro_aggregate_expr :: proc(
|
||||||
|
state: ^State,
|
||||||
|
result: ^cimport.Result,
|
||||||
|
macro: cimport.Macro_Constant,
|
||||||
|
record_type: types.Type,
|
||||||
|
pkg: ast.Package_Id,
|
||||||
|
record_mapping: []types.Type,
|
||||||
|
type_mapping: []types.Type,
|
||||||
|
span: source.Span,
|
||||||
|
) -> (ast.Expr_Id, bool) {
|
||||||
|
fields := types.fields_for(&state.module.type_store, record_type)
|
||||||
|
union_record := types.is_union(record_type, &state.module.type_store)
|
||||||
|
initializer_count := 1 if union_record else len(fields)
|
||||||
|
if len(fields) == 0 || len(macro.values) > initializer_count {
|
||||||
|
return ast.INVALID_EXPR, false
|
||||||
|
}
|
||||||
|
converted := make([]Converted_Macro_Value, len(macro.values), state.allocator)
|
||||||
|
defer delete(converted, state.allocator)
|
||||||
|
for value, index in macro.values {
|
||||||
|
converted[index], _ = convert_macro_field_value(
|
||||||
|
state,
|
||||||
|
result,
|
||||||
|
value,
|
||||||
|
fields[index].type,
|
||||||
|
pkg,
|
||||||
|
record_mapping,
|
||||||
|
type_mapping,
|
||||||
|
)
|
||||||
|
if converted[index].kind == .Invalid {
|
||||||
|
return ast.INVALID_EXPR, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
args := make([]ast.Expr_Id, initializer_count, state.allocator)
|
||||||
|
remaining := MAX_MACRO_ZERO_NODES
|
||||||
|
for index in 0..<initializer_count {
|
||||||
|
field := fields[index]
|
||||||
|
value_expr := ast.INVALID_EXPR
|
||||||
|
value_ok := false
|
||||||
|
if index < len(macro.values) {
|
||||||
|
value_expr, value_ok = add_converted_macro_value_expr(
|
||||||
|
state, converted[index], span,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
value_expr, value_ok = add_macro_zero_expr(
|
||||||
|
state, pkg, field.type, span, 0, &remaining,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if !value_ok {
|
||||||
|
delete(args, state.allocator)
|
||||||
|
return ast.INVALID_EXPR, false
|
||||||
|
}
|
||||||
|
args[index] = add_import_expr(state, ast.Expr{
|
||||||
|
kind=.Keyed, span=span, name=symbol.Id(field.name), left=value_expr,
|
||||||
|
right=ast.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return add_import_expr(state, ast.Expr{
|
||||||
|
kind=.Struct_Literal,
|
||||||
|
span=span,
|
||||||
|
name=symbol.intern(state.symbols, macro.type_name),
|
||||||
|
args=args,
|
||||||
|
left=ast.INVALID_EXPR,
|
||||||
|
right=ast.INVALID_EXPR,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
}), true
|
||||||
|
}
|
||||||
|
|
||||||
|
find_global_in_package :: proc(module: ^ast.Module, pkg: ast.Package_Id, name: symbol.Id) -> (ast.Global_Id, bool) {
|
||||||
|
for global, index in module.globals {
|
||||||
|
if global.pkg == pkg && global.name == name {
|
||||||
|
return ast.global_id(index), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ast.INVALID_GLOBAL, false
|
||||||
|
}
|
||||||
|
|
||||||
|
find_function_in_package :: proc(module: ^ast.Module, pkg: ast.Package_Id, name: symbol.Id) -> bool {
|
||||||
|
for function in module.functions {
|
||||||
|
if function.pkg == pkg && function.name == name {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_value_declarations_in_package :: proc(
|
||||||
|
state: ^State,
|
||||||
|
pkg: ast.Package_Id,
|
||||||
|
name: symbol.Id,
|
||||||
|
) {
|
||||||
|
function_index := 0
|
||||||
|
for function_index < len(state.module.functions) {
|
||||||
|
function := state.module.functions[function_index]
|
||||||
|
if function.pkg != pkg || function.name != name {
|
||||||
|
function_index += 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
delete(function.params, state.allocator)
|
||||||
|
delete(function.body, state.allocator)
|
||||||
|
delete(function.link_name, state.allocator)
|
||||||
|
delete(function.unsupported_reason, state.allocator)
|
||||||
|
ordered_remove(&state.module.functions, function_index)
|
||||||
|
}
|
||||||
|
global_index := 0
|
||||||
|
for global_index < len(state.module.globals) {
|
||||||
|
global := state.module.globals[global_index]
|
||||||
|
if global.pkg != pkg || global.name != name {
|
||||||
|
global_index += 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
delete(global.link_name, state.allocator)
|
||||||
|
ordered_remove(&state.module.globals, global_index)
|
||||||
|
}
|
||||||
|
unsupported_index := 0
|
||||||
|
for unsupported_index < len(state.module.unsupported) {
|
||||||
|
item := state.module.unsupported[unsupported_index]
|
||||||
|
if item.pkg != pkg || item.name != name {
|
||||||
|
unsupported_index += 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
delete(item.reason, state.allocator)
|
||||||
|
ordered_remove(&state.module.unsupported, unsupported_index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
add_external_variable_global :: proc(
|
||||||
|
state: ^State,
|
||||||
|
result: ^cimport.Result,
|
||||||
|
pkg: ast.Package_Id,
|
||||||
|
variable: cimport.Variable,
|
||||||
|
record_mapping: []types.Type,
|
||||||
|
type_mapping: []types.Type,
|
||||||
|
span: source.Span,
|
||||||
|
) {
|
||||||
|
name := symbol.intern(state.symbols, variable.name)
|
||||||
|
variable_type := translate_c_type(state, result, variable.type, pkg, record_mapping, type_mapping)
|
||||||
|
unsupported_reason := variable.reason
|
||||||
|
if len(unsupported_reason) == 0 {
|
||||||
|
unsupported_reason = c_record_by_value_reason(result, variable.type)
|
||||||
|
}
|
||||||
|
if len(unsupported_reason) == 0 && !types.is_runtime_value(variable_type, &state.module.type_store) {
|
||||||
|
unsupported_reason = "C variable type is not supported"
|
||||||
|
}
|
||||||
|
if len(unsupported_reason) > 0 {
|
||||||
|
add_import_unsupported(state, pkg, variable.name, unsupported_reason)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if find_function_in_package(state.module, pkg, name) {
|
||||||
|
add_import_unsupported(state, pkg, variable.name, "C variable conflicts with a function declaration")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if existing, ok := find_global_in_package(state.module, pkg, name); ok {
|
||||||
|
global := state.module.globals[existing]
|
||||||
|
if !global.external || !types.equal(global.type, variable_type) || global.writable != variable.mutable {
|
||||||
|
add_import_unsupported(state, pkg, variable.name, "conflicting C declarations for variable")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_ = ast.global_id(len(state.module.globals))
|
||||||
|
append(&state.module.globals, ast.Global{
|
||||||
|
span=span,
|
||||||
|
name=name,
|
||||||
|
link_name=strings.clone(variable.name, state.allocator),
|
||||||
|
pkg=pkg,
|
||||||
|
file=ast.INVALID_FILE,
|
||||||
|
type=variable_type,
|
||||||
|
immutable=true,
|
||||||
|
external=true,
|
||||||
|
writable=variable.mutable,
|
||||||
|
expr=ast.INVALID_EXPR,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
add_macro_constant_global :: proc(
|
||||||
|
state: ^State,
|
||||||
|
result: ^cimport.Result,
|
||||||
|
pkg: ast.Package_Id,
|
||||||
|
macro: cimport.Macro_Constant,
|
||||||
|
record_mapping: []types.Type,
|
||||||
|
type_mapping: []types.Type,
|
||||||
|
span: source.Span,
|
||||||
|
) {
|
||||||
|
name := symbol.intern(state.symbols, macro.name)
|
||||||
|
remove_value_declarations_in_package(state, pkg, name)
|
||||||
|
if macro.aggregate {
|
||||||
|
type_name := symbol.intern(state.symbols, macro.type_name)
|
||||||
|
named := types.find_named(&state.module.type_store, u32(pkg), u32(type_name))
|
||||||
|
record_type := types.resolve_alias(named, &state.module.type_store)
|
||||||
|
if !types.is_record(record_type, &state.module.type_store) ||
|
||||||
|
types.is_opaque_struct(record_type, &state.module.type_store) {
|
||||||
|
add_import_unsupported(state, pkg, macro.name, "C macro aggregate type is not supported")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
expr, ok := add_macro_aggregate_expr(
|
||||||
|
state,
|
||||||
|
result,
|
||||||
|
macro,
|
||||||
|
record_type,
|
||||||
|
pkg,
|
||||||
|
record_mapping,
|
||||||
|
type_mapping,
|
||||||
|
span,
|
||||||
|
)
|
||||||
|
if !ok {
|
||||||
|
add_import_unsupported(state, pkg, macro.name, "C macro aggregate initializer is not representable")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_ = ast.global_id(len(state.module.globals))
|
||||||
|
append(&state.module.globals, ast.Global{
|
||||||
|
span=span,
|
||||||
|
name=name,
|
||||||
|
pkg=pkg,
|
||||||
|
file=ast.INVALID_FILE,
|
||||||
|
type=record_type,
|
||||||
|
immutable=true,
|
||||||
|
expr=expr,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
macro_type := translate_c_type(state, result, macro.type, pkg, record_mapping, type_mapping)
|
||||||
|
if !types.is_runtime_value(macro_type, &state.module.type_store) {
|
||||||
|
add_import_unsupported(state, pkg, macro.name, "C macro constant type is not supported")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
expr := add_macro_value_expr(state, macro.value, span)
|
||||||
|
_ = ast.global_id(len(state.module.globals))
|
||||||
|
append(&state.module.globals, ast.Global{
|
||||||
|
span=span,
|
||||||
|
name=name,
|
||||||
|
pkg=pkg,
|
||||||
|
file=ast.INVALID_FILE,
|
||||||
|
type=macro_type,
|
||||||
|
immutable=true,
|
||||||
|
expr=expr,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
load_header :: proc(state: ^State, path: string, import_span: source.Span) -> ast.Package_Id {
|
load_header :: proc(state: ^State, path: string, import_span: source.Span) -> ast.Package_Id {
|
||||||
canonical, ok := filepath.abs(path, state.allocator)
|
canonical, ok := filepath.abs(path, state.allocator)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -460,14 +1080,38 @@ load_header :: proc(state: ^State, path: string, import_span: source.Span) -> as
|
|||||||
variadic=function.variadic,
|
variadic=function.variadic,
|
||||||
params=params,
|
params=params,
|
||||||
result=function_result,
|
result=function_result,
|
||||||
|
link_name=strings.clone(function.link_name, state.allocator),
|
||||||
unsupported_reason=strings.clone(unsupported_reason, state.allocator),
|
unsupported_reason=strings.clone(unsupported_reason, state.allocator),
|
||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
|
// Emit the wrapper only for a `static inline` that survives as supported.
|
||||||
|
// cimport may translate its signature fine yet the by-value record layout
|
||||||
|
// checks above can still reject it; a wrapper for an uncallable function
|
||||||
|
// would just be dead external code.
|
||||||
|
if len(unsupported_reason) == 0 && len(function.link_name) > 0 {
|
||||||
|
if trampoline, ok := find_trampoline(&result, function.link_name); ok {
|
||||||
|
add_c_trampoline(state, trampoline)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for variable in result.variables {
|
||||||
|
add_external_variable_global(
|
||||||
|
state, &result, pkg_id, variable, record_mapping, type_mapping, import_span,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
for macro in result.macros {
|
||||||
|
add_macro_constant_global(
|
||||||
|
state, &result, pkg_id, macro, record_mapping, type_mapping, import_span,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
for item in result.unsupported {
|
for item in result.unsupported {
|
||||||
|
name := symbol.intern(state.symbols, item.name)
|
||||||
|
if item.final_macro {
|
||||||
|
remove_value_declarations_in_package(state, pkg_id, name)
|
||||||
|
}
|
||||||
append(&state.module.unsupported, ast.Unsupported{
|
append(&state.module.unsupported, ast.Unsupported{
|
||||||
pkg=pkg_id,
|
pkg=pkg_id,
|
||||||
name=symbol.intern(state.symbols, item.name),
|
name=name,
|
||||||
reason=strings.clone(item.reason, state.allocator),
|
reason=strings.clone(item.reason, state.allocator),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -644,10 +644,13 @@ lower :: proc(hir_module: ^hir.Module, allocator := context.allocator) -> ir.Mod
|
|||||||
_ = ir.global_id(len(module.globals))
|
_ = ir.global_id(len(module.globals))
|
||||||
append(&module.globals, ir.Global{
|
append(&module.globals, ir.Global{
|
||||||
name=global.name,
|
name=global.name,
|
||||||
|
link_name=fmt.aprintf("%s", global.link_name, allocator=allocator),
|
||||||
type=global.type,
|
type=global.type,
|
||||||
is_static=global.is_static,
|
is_static=global.is_static,
|
||||||
|
external=global.external,
|
||||||
|
writable=global.writable,
|
||||||
static_value=global.static_value,
|
static_value=global.static_value,
|
||||||
initializer=nil if global.is_static else lower_global_initializer(hir_module, global, allocator),
|
initializer=nil if global.is_static || global.external else lower_global_initializer(hir_module, global, allocator),
|
||||||
problematic=global.problematic,
|
problematic=global.problematic,
|
||||||
diagnostic=global.diagnostic,
|
diagnostic=global.diagnostic,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -992,6 +992,31 @@ main :: func() void {
|
|||||||
testing.expect(t, sentinel_error)
|
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)
|
@(test)
|
||||||
c_variadic_calls_promote_extras_and_emit_variadic_llvm :: proc(t: ^testing.T) {
|
c_variadic_calls_promote_extras_and_emit_variadic_llvm :: proc(t: ^testing.T) {
|
||||||
text := `variadic :: c_func(tag c_int, ...) c_int
|
text := `variadic :: c_func(tag c_int, ...) c_int
|
||||||
@@ -2118,6 +2143,14 @@ Fake_Cimport_State :: struct {
|
|||||||
saw_options: bool,
|
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 {
|
fake_cimport_backend :: proc(user_data: rawptr, request: cimport.Request, allocator: mem.Allocator) -> cimport.Result {
|
||||||
state := (^Fake_Cimport_State)(user_data)
|
state := (^Fake_Cimport_State)(user_data)
|
||||||
state.calls += 1
|
state.calls += 1
|
||||||
@@ -2142,10 +2175,143 @@ fake_cimport_backend :: proc(user_data: rawptr, request: cimport.Request, alloca
|
|||||||
variadic=true,
|
variadic=true,
|
||||||
reason=fmt.aprintf("", allocator=allocator),
|
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
|
result.available = true
|
||||||
return result
|
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)
|
@(test)
|
||||||
cimport_backend_is_replaceable :: proc(t: ^testing.T) {
|
cimport_backend_is_replaceable :: proc(t: ^testing.T) {
|
||||||
state := Fake_Cimport_State{available=true}
|
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(t, result.available)
|
||||||
testing.expect_value(t, state.calls, 1)
|
testing.expect_value(t, state.calls, 1)
|
||||||
testing.expect_value(t, len(result.functions), 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.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)
|
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)
|
@(test)
|
||||||
loader_injects_and_caches_cimport_backend_per_compilation :: proc(t: ^testing.T) {
|
loader_injects_and_caches_cimport_backend_per_compilation :: proc(t: ^testing.T) {
|
||||||
sources := source.init_store()
|
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
|
found_variadic = found_variadic || function.variadic
|
||||||
}
|
}
|
||||||
testing.expect(t, found_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)
|
@(test)
|
||||||
|
|||||||
@@ -21,6 +21,37 @@ main :: func() void {
|
|||||||
_ = native.imported_apply(double_value, 21)
|
_ = native.imported_apply(double_value, 21)
|
||||||
_ = call_mapper(double_value)
|
_ = call_mapper(double_value)
|
||||||
_ = native.configured_value(9)
|
_ = native.configured_value(9)
|
||||||
|
_ = native.IMPORTED_SHADOW_OBJECT
|
||||||
|
_ = native.IMPORTED_SHADOW_FUNCTION
|
||||||
|
native.imported_global = native.IMPORTED_MAGIC
|
||||||
|
native.imported_record_global.value = native.IMPORTED_MAGIC
|
||||||
|
color native.Imported_Color :: native.IMPORTED_COLOR
|
||||||
|
_ = native.imported_check_state(
|
||||||
|
color,
|
||||||
|
native.IMPORTED_MAGIC,
|
||||||
|
native.imported_const_global,
|
||||||
|
native.imported_global,
|
||||||
|
native.imported_record_global.value,
|
||||||
|
native.IMPORTED_OCTAL,
|
||||||
|
native.IMPORTED_UINT,
|
||||||
|
native.IMPORTED_REDEFINED,
|
||||||
|
native.IMPORTED_ACTIVE_AFTER_INACTIVE_UNDEF,
|
||||||
|
native.IMPORTED_REPEAT,
|
||||||
|
native.IMPORTED_HEX_E,
|
||||||
|
native.IMPORTED_NEG_DECIMAL,
|
||||||
|
native.IMPORTED_NEG_HEX,
|
||||||
|
native.IMPORTED_NEG_UINT,
|
||||||
|
native.IMPORTED_FLOAT,
|
||||||
|
native.IMPORTED_DOUBLE,
|
||||||
|
)
|
||||||
|
partial native.Imported_Color :: native.IMPORTED_PARTIAL_COLOR
|
||||||
|
nested native.Imported_Zero_Outer :: native.IMPORTED_ZERO_NESTED
|
||||||
|
first_union native.Imported_Zero_Union :: native.IMPORTED_FIRST_UNION
|
||||||
|
_ = native.imported_check_zero_state(partial, nested, first_union)
|
||||||
|
native.imported_mutable_array_record.values[0] = 42
|
||||||
|
_ = native.imported_check_array_record()
|
||||||
|
conversions native.Imported_Conversions :: native.IMPORTED_CONVERSIONS
|
||||||
|
_ = native.imported_check_conversions(conversions)
|
||||||
signed i8 :: -2
|
signed i8 :: -2
|
||||||
unsigned u16 :: 3
|
unsigned u16 :: 3
|
||||||
float_value f32 :: 4.0
|
float_value f32 :: 4.0
|
||||||
@@ -28,4 +59,7 @@ main :: func() void {
|
|||||||
pointer *u8 :: "ok".ptr
|
pointer *u8 :: "ok".ptr
|
||||||
nullable ?*u8 :: pointer
|
nullable ?*u8 :: pointer
|
||||||
_ = native.imported_variadic(7, signed, unsigned, float_value, c_float_value, pointer, nullable)
|
_ = native.imported_variadic(7, signed, unsigned, float_value, c_float_value, pointer, nullable)
|
||||||
|
inline_scalar c_int :: native.imported_inline(5)
|
||||||
|
inline_record native.Imported_Value :: native.imported_inline_record(native.Imported_Value { value = 7 })
|
||||||
|
_ = native.imported_check_inline(inline_scalar, inline_record.value)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,5 +2,6 @@
|
|||||||
#define BROLANG_CHILD_H
|
#define BROLANG_CHILD_H
|
||||||
|
|
||||||
int child_value(int value);
|
int child_value(int value);
|
||||||
|
extern int child_shared_global;
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
#ifndef BROLANG_GUARDED_H
|
||||||
|
#define BROLANG_GUARDED_H
|
||||||
|
|
||||||
|
#define IMPORTED_GUARDED 321
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -2,6 +2,15 @@
|
|||||||
#define BROLANG_NATIVE_H
|
#define BROLANG_NATIVE_H
|
||||||
|
|
||||||
#include <child.h>
|
#include <child.h>
|
||||||
|
#include "repeat.h"
|
||||||
|
#undef IMPORTED_REPEAT
|
||||||
|
#include "repeat.h"
|
||||||
|
#include "guarded.h"
|
||||||
|
#undef IMPORTED_GUARDED
|
||||||
|
#include "guarded.h"
|
||||||
|
#include "once.h"
|
||||||
|
#undef IMPORTED_ONCE
|
||||||
|
#include "once.h"
|
||||||
|
|
||||||
typedef int imported_int;
|
typedef int imported_int;
|
||||||
typedef imported_int imported_int_alias;
|
typedef imported_int imported_int_alias;
|
||||||
@@ -11,9 +20,43 @@ typedef int (*Imported_Mapper)(int value);
|
|||||||
typedef struct Imported_Value {
|
typedef struct Imported_Value {
|
||||||
int value;
|
int value;
|
||||||
} Imported_Value;
|
} Imported_Value;
|
||||||
|
typedef struct Imported_Color {
|
||||||
|
unsigned char r;
|
||||||
|
unsigned char g;
|
||||||
|
unsigned char b;
|
||||||
|
unsigned char a;
|
||||||
|
} Imported_Color;
|
||||||
|
typedef struct Imported_Zero_Inner {
|
||||||
|
float weight;
|
||||||
|
int values[2];
|
||||||
|
const char *label;
|
||||||
|
} Imported_Zero_Inner;
|
||||||
|
typedef union Imported_Zero_Union {
|
||||||
|
int integer;
|
||||||
|
double decimal;
|
||||||
|
} Imported_Zero_Union;
|
||||||
|
typedef struct Imported_Zero_Outer {
|
||||||
|
int head;
|
||||||
|
Imported_Zero_Inner inner;
|
||||||
|
Imported_Zero_Union choice;
|
||||||
|
} Imported_Zero_Outer;
|
||||||
|
typedef struct Imported_Array_Record {
|
||||||
|
int values[2];
|
||||||
|
} Imported_Array_Record;
|
||||||
|
typedef struct Imported_Conversions {
|
||||||
|
unsigned char wrapped;
|
||||||
|
int truncated;
|
||||||
|
float from_integer;
|
||||||
|
double from_float;
|
||||||
|
const char *pointer;
|
||||||
|
} Imported_Conversions;
|
||||||
|
typedef struct Imported_Signed_Narrow {
|
||||||
|
signed char value;
|
||||||
|
} Imported_Signed_Narrow;
|
||||||
typedef union Imported_Union {
|
typedef union Imported_Union {
|
||||||
int value;
|
int value;
|
||||||
} Imported_Union;
|
} Imported_Union;
|
||||||
|
typedef int Imported_Const_Array[2];
|
||||||
typedef struct Imported_Bitfield {
|
typedef struct Imported_Bitfield {
|
||||||
unsigned value : 1;
|
unsigned value : 1;
|
||||||
} Imported_Bitfield;
|
} Imported_Bitfield;
|
||||||
@@ -47,14 +90,103 @@ int imported_read(const Imported_Handle *handle);
|
|||||||
int imported_string(const char *value);
|
int imported_string(const char *value);
|
||||||
int imported_apply(Imported_Mapper mapper, int value);
|
int imported_apply(Imported_Mapper mapper, int value);
|
||||||
Imported_Value imported_by_value(Imported_Value value);
|
Imported_Value imported_by_value(Imported_Value value);
|
||||||
|
int imported_check_state(
|
||||||
|
Imported_Color color,
|
||||||
|
int macro_value,
|
||||||
|
int const_value,
|
||||||
|
int global_value,
|
||||||
|
int record_value,
|
||||||
|
int octal_value,
|
||||||
|
unsigned int uint_value,
|
||||||
|
int redefined_value,
|
||||||
|
int inactive_undef_value,
|
||||||
|
int repeated_value,
|
||||||
|
unsigned int hex_e_value,
|
||||||
|
long negative_decimal_value,
|
||||||
|
unsigned int negative_hex_value,
|
||||||
|
unsigned int negative_uint_value,
|
||||||
|
float float_value,
|
||||||
|
double double_value
|
||||||
|
);
|
||||||
|
int imported_check_zero_state(
|
||||||
|
Imported_Color partial,
|
||||||
|
Imported_Zero_Outer nested,
|
||||||
|
Imported_Zero_Union first_union
|
||||||
|
);
|
||||||
|
int imported_check_array_record(void);
|
||||||
|
int imported_check_conversions(Imported_Conversions value);
|
||||||
int imported_volatile(volatile int *value);
|
int imported_volatile(volatile int *value);
|
||||||
_Bool imported_bool(_Bool value);
|
_Bool imported_bool(_Bool value);
|
||||||
extern int imported_global;
|
extern int imported_global;
|
||||||
|
extern const int imported_const_global;
|
||||||
|
extern const int imported_const_array[2];
|
||||||
|
extern const Imported_Const_Array imported_typedef_const_array;
|
||||||
|
extern int imported_redeclared_array[];
|
||||||
|
extern int imported_redeclared_array[4];
|
||||||
|
extern Imported_Value imported_record_global;
|
||||||
|
extern const Imported_Array_Record imported_const_array_record;
|
||||||
|
extern Imported_Array_Record imported_mutable_array_record;
|
||||||
|
extern _Thread_local int imported_tls_global;
|
||||||
|
extern int IMPORTED_SHADOW_OBJECT;
|
||||||
|
int IMPORTED_SHADOW_FUNCTION(void);
|
||||||
|
extern int IMPORTED_SHADOW_UNSUPPORTED;
|
||||||
|
extern int IMPORTED_EMPTY_SHADOW;
|
||||||
|
|
||||||
|
#define CLITERAL(type) (type)
|
||||||
|
#define IMPORTED_SHADOW_OBJECT 71
|
||||||
|
#define IMPORTED_SHADOW_FUNCTION 72
|
||||||
|
#define IMPORTED_SHADOW_UNSUPPORTED (1 + 2)
|
||||||
|
#define IMPORTED_MAGIC 42
|
||||||
|
#define IMPORTED_COLOR CLITERAL(Imported_Color){ 255, 255, 255, 255 }
|
||||||
|
#define IMPORTED_PARTIAL_COLOR CLITERAL(Imported_Color){ 7 }
|
||||||
|
#define IMPORTED_ZERO_NESTED CLITERAL(Imported_Zero_Outer){ 5 }
|
||||||
|
#define IMPORTED_FIRST_UNION CLITERAL(Imported_Zero_Union){ 9 }
|
||||||
|
#define IMPORTED_CONVERSIONS CLITERAL(Imported_Conversions){ -1, 3.75, 42, 16777217.0f, 0 }
|
||||||
|
#define IMPORTED_SIGNED_NARROW_BAD CLITERAL(Imported_Signed_Narrow){ 128 }
|
||||||
|
#define IMPORTED_TOO_MANY_COLOR CLITERAL(Imported_Color){ 1, 2, 3, 4, 5 }
|
||||||
|
#define IMPORTED_BAD_EXPR (1 + 2)
|
||||||
|
#define IMPORTED_OCTAL 010
|
||||||
|
#define IMPORTED_UINT 4294967295U
|
||||||
|
#define IMPORTED_HEX_E 0xDEADBEEF
|
||||||
|
#define IMPORTED_NEG_DECIMAL -2147483648
|
||||||
|
#define IMPORTED_NEG_HEX -0x80000000
|
||||||
|
#define IMPORTED_NEG_UINT -1U
|
||||||
|
#define IMPORTED_FLOAT 2.5f
|
||||||
|
#define IMPORTED_DOUBLE 6.25
|
||||||
|
#define IMPORTED_FLOAT_OVERFLOW 1e40f
|
||||||
|
#define IMPORTED_EMPTY_SHADOW
|
||||||
|
#define IMPORTED_REDEFINED 1
|
||||||
|
#undef IMPORTED_REDEFINED
|
||||||
|
#define IMPORTED_REDEFINED 2
|
||||||
|
#define IMPORTED_GONE 3
|
||||||
|
#undef IMPORTED_GONE
|
||||||
|
#define IMPORTED_REDEFINED_BAD 1
|
||||||
|
#undef IMPORTED_REDEFINED_BAD
|
||||||
|
#define IMPORTED_REDEFINED_BAD (1 + 2)
|
||||||
|
#define IMPORTED_ACTIVE_AFTER_INACTIVE_UNDEF 77
|
||||||
|
#if 0
|
||||||
|
#undef IMPORTED_ACTIVE_AFTER_INACTIVE_UNDEF
|
||||||
|
#endif
|
||||||
|
|
||||||
static inline int imported_inline(int value) {
|
static inline int imported_inline(int value) {
|
||||||
return value;
|
return value + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline Imported_Value imported_inline_record(Imported_Value value) {
|
||||||
|
Imported_Value result = { value.value * 2 };
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int imported_inline_bad_layout(Imported_Packed value) {
|
||||||
|
return value.first;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int imported_inline_variadic(int marker, ...) {
|
||||||
|
return marker;
|
||||||
|
}
|
||||||
|
|
||||||
|
int imported_check_inline(int scalar, int record);
|
||||||
|
|
||||||
#ifdef BROLANG_FEATURE
|
#ifdef BROLANG_FEATURE
|
||||||
int configured_value(int value);
|
int configured_value(int value);
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define IMPORTED_ONCE 456
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
#define IMPORTED_REPEAT 123
|
||||||
@@ -8,6 +8,15 @@ struct Imported_Handle {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static struct Imported_Handle handle = {7};
|
static struct Imported_Handle handle = {7};
|
||||||
|
int imported_global = 0;
|
||||||
|
const int imported_const_global = 9;
|
||||||
|
const int imported_const_array[2] = {10, 20};
|
||||||
|
const Imported_Const_Array imported_typedef_const_array = {11, 12};
|
||||||
|
int imported_redeclared_array[4] = {13, 14, 15, 16};
|
||||||
|
Imported_Value imported_record_global = {40};
|
||||||
|
const Imported_Array_Record imported_const_array_record = {{20, 21}};
|
||||||
|
Imported_Array_Record imported_mutable_array_record = {{0, 1}};
|
||||||
|
int child_shared_global = 0;
|
||||||
|
|
||||||
int child_value(int value) {
|
int child_value(int value) {
|
||||||
return value;
|
return value;
|
||||||
@@ -47,6 +56,97 @@ int imported_apply(Imported_Mapper mapper, int value) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int imported_check_state(
|
||||||
|
Imported_Color color,
|
||||||
|
int macro_value,
|
||||||
|
int const_value,
|
||||||
|
int global_value,
|
||||||
|
int record_value,
|
||||||
|
int octal_value,
|
||||||
|
unsigned int uint_value,
|
||||||
|
int redefined_value,
|
||||||
|
int inactive_undef_value,
|
||||||
|
int repeated_value,
|
||||||
|
unsigned int hex_e_value,
|
||||||
|
long negative_decimal_value,
|
||||||
|
unsigned int negative_hex_value,
|
||||||
|
unsigned int negative_uint_value,
|
||||||
|
float float_value,
|
||||||
|
double double_value
|
||||||
|
) {
|
||||||
|
if (!(color.r == 255 &&
|
||||||
|
color.g == 255 &&
|
||||||
|
color.b == 255 &&
|
||||||
|
color.a == 255 &&
|
||||||
|
macro_value == 42 &&
|
||||||
|
const_value == 9 &&
|
||||||
|
global_value == 42 &&
|
||||||
|
record_value == 42 &&
|
||||||
|
octal_value == 8 &&
|
||||||
|
uint_value == 4294967295U &&
|
||||||
|
redefined_value == 2 &&
|
||||||
|
inactive_undef_value == 77 &&
|
||||||
|
repeated_value == 123 &&
|
||||||
|
hex_e_value == 0xDEADBEEF &&
|
||||||
|
negative_decimal_value == -2147483648L &&
|
||||||
|
negative_hex_value == 0x80000000U &&
|
||||||
|
negative_uint_value == 0xFFFFFFFFU &&
|
||||||
|
float_value == 2.5f &&
|
||||||
|
double_value == 6.25)) {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int imported_check_zero_state(
|
||||||
|
Imported_Color partial,
|
||||||
|
Imported_Zero_Outer nested,
|
||||||
|
Imported_Zero_Union first_union
|
||||||
|
) {
|
||||||
|
if (!(partial.r == 7 &&
|
||||||
|
partial.g == 0 &&
|
||||||
|
partial.b == 0 &&
|
||||||
|
partial.a == 0 &&
|
||||||
|
nested.head == 5 &&
|
||||||
|
nested.inner.weight == 0.0f &&
|
||||||
|
nested.inner.values[0] == 0 &&
|
||||||
|
nested.inner.values[1] == 0 &&
|
||||||
|
nested.inner.label == NULL &&
|
||||||
|
nested.choice.integer == 0 &&
|
||||||
|
first_union.integer == 9)) {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int imported_check_array_record(void) {
|
||||||
|
if (!(imported_const_array_record.values[0] == 20 &&
|
||||||
|
imported_const_array_record.values[1] == 21 &&
|
||||||
|
imported_mutable_array_record.values[0] == 42 &&
|
||||||
|
imported_mutable_array_record.values[1] == 1)) {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int imported_check_conversions(Imported_Conversions value) {
|
||||||
|
if (!(value.wrapped == 255 &&
|
||||||
|
value.truncated == 3 &&
|
||||||
|
value.from_integer == 42.0f &&
|
||||||
|
value.from_float == 16777216.0 &&
|
||||||
|
value.pointer == NULL)) {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int imported_check_inline(int scalar, int record) {
|
||||||
|
if (!(scalar == 6 && record == 14)) {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
int imported_variadic(int marker, ...) {
|
int imported_variadic(int marker, ...) {
|
||||||
va_list args;
|
va_list args;
|
||||||
va_start(args, marker);
|
va_start(args, marker);
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
/* Imported through a fake cimport backend in compiler tests. */
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
first :: import "first.h"
|
||||||
|
second :: import "second.h"
|
||||||
|
|
||||||
|
main :: func() void {
|
||||||
|
_ = first.conflict_global
|
||||||
|
_ = second.conflict_global
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
/* Imported through a fake cimport backend in compiler tests. */
|
||||||
@@ -4,4 +4,7 @@ child :: import "../header/include/child.h"
|
|||||||
main :: func() void {
|
main :: func() void {
|
||||||
_ = native.child_value(1)
|
_ = native.child_value(1)
|
||||||
_ = child.child_value(2)
|
_ = child.child_value(2)
|
||||||
|
native.child_shared_global = 7
|
||||||
|
child.child_shared_global = native.child_shared_global
|
||||||
|
_ = native.child_shared_global + child.child_shared_global
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
native :: import "native.h"
|
||||||
|
|
||||||
|
main :: func() void {
|
||||||
|
_ = native.main
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
/* Imported through a fake cimport backend in compiler tests. */
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
native :: import "native.h"
|
||||||
|
|
||||||
|
value :: native.main
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
/* Imported through a fake cimport backend in compiler tests. */
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
/* Imported through a fake cimport backend in compiler tests. */
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
variable :: import "variable.h"
|
||||||
|
function :: import "function.h"
|
||||||
|
|
||||||
|
main :: func() void {
|
||||||
|
_ = variable.conflict_symbol
|
||||||
|
_ = function.conflict_symbol()
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
/* Imported through a fake cimport backend in compiler tests. */
|
||||||
@@ -6,7 +6,14 @@ use_enum :: c_func(value native.Imported_Enum) void
|
|||||||
use_opaque :: c_func(value native.Imported_Handle) void
|
use_opaque :: c_func(value native.Imported_Handle) void
|
||||||
|
|
||||||
main :: func() void {
|
main :: func() void {
|
||||||
_ = native.IMPORTED_MACRO
|
_ = native.IMPORTED_BAD_EXPR
|
||||||
|
_ = native.IMPORTED_REDEFINED_BAD
|
||||||
|
_ = native.IMPORTED_GONE
|
||||||
|
_ = native.IMPORTED_TOO_MANY_COLOR
|
||||||
|
_ = native.IMPORTED_SIGNED_NARROW_BAD
|
||||||
|
_ = native.IMPORTED_SHADOW_UNSUPPORTED
|
||||||
|
_ = native.IMPORTED_FLOAT_OVERFLOW
|
||||||
|
_ = native.IMPORTED_EMPTY_SHADOW
|
||||||
_ = native.imported_by_value()
|
_ = native.imported_by_value()
|
||||||
_ = native.imported_bitfield()
|
_ = native.imported_bitfield()
|
||||||
_ = native.imported_packed()
|
_ = native.imported_packed()
|
||||||
@@ -16,6 +23,11 @@ main :: func() void {
|
|||||||
_ = native.imported_anonymous()
|
_ = native.imported_anonymous()
|
||||||
_ = native.imported_volatile()
|
_ = native.imported_volatile()
|
||||||
_ = native.imported_bool()
|
_ = native.imported_bool()
|
||||||
_ = native.imported_global
|
native.imported_const_global = 1
|
||||||
_ = native.imported_inline(1)
|
native.imported_const_array = [1, 2]
|
||||||
|
native.imported_typedef_const_array = [1, 2]
|
||||||
|
native.imported_typedef_const_array[0] = 1
|
||||||
|
native.imported_const_array_record.values[0] = 42
|
||||||
|
_ = native.imported_tls_global
|
||||||
|
_ = native.imported_inline_variadic(1)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
native :: import "native.h"
|
||||||
|
|
||||||
|
main :: func() void {
|
||||||
|
_ = native.write
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
/* Imported through a fake cimport backend in compiler tests. */
|
||||||
Reference in New Issue
Block a user