function pointers and callbacks

This commit is contained in:
2026-06-15 21:09:46 +02:00
parent 3b7c3fcbd0
commit f5605fd3ec
21 changed files with 1927 additions and 122 deletions
+168 -4
View File
@@ -168,6 +168,26 @@ translate_c_type :: proc(
pointer := types.pointer(&state.module.type_store, child, item.mutable, true)
translated = types.optional(&state.module.type_store, pointer)
}
case .Array:
child := translate_c_type(state, result, item.child, pkg, record_mapping, type_mapping)
if types.is_valid(child) {
translated = types.array(&state.module.type_store, child, item.count, item.mutable)
}
case .Function:
params := make([]types.Type, len(item.params), state.allocator)
for param, index in item.params {
params[index] = translate_c_type(state, result, param, pkg, record_mapping, type_mapping)
if !types.is_valid(params[index]) {
delete(params, state.allocator)
type_mapping[value] = types.INVALID
return types.INVALID
}
}
result_type := translate_c_type(state, result, item.child, pkg, record_mapping, type_mapping)
if types.is_valid(result_type) {
translated = types.function(&state.module.type_store, params, result_type, true, item.variadic)
}
delete(params, state.allocator)
case .Record:
if int(item.record) < len(record_mapping) {
translated = record_mapping[item.record]
@@ -189,6 +209,93 @@ function_signatures_equal :: proc(left: ast.Function, params: []ast.Param, resul
return true
}
RECORD_DEPENDENCY_PENDING :: "C record contains an incomplete or unsupported record field"
record_layout_reason :: proc(
store: ^types.Store,
record: cimport.Record,
fields: []types.Field,
selected: target.Target,
) -> string {
if len(fields) == 0 {
if record.size > 0 {
return "anonymous C record fields are not supported"
}
return "empty C records are not supported"
}
if len(fields) != len(record.fields) || record.alignment == 0 {
return "C record metadata is incomplete"
}
size: u64
alignment: u64 = 1
if record.kind == .Union {
for field in fields {
if !types.is_runtime_value(field.type, store) {
return RECORD_DEPENDENCY_PENDING
}
if field.offset != 0 {
return "non-natural C record layouts are not supported"
}
size = max(size, types.size(field.type, store, selected))
alignment = max(alignment, u64(types.alignment_of(field.type, store, selected)))
}
} else {
offset: u64
for field in fields {
if !types.is_runtime_value(field.type, store) {
return RECORD_DEPENDENCY_PENDING
}
field_alignment := u64(types.alignment_of(field.type, store, selected))
offset = (offset+field_alignment-1)/field_alignment*field_alignment
if field.offset != offset {
if field.offset < offset {
return "packed C records are not supported"
}
return "non-natural C record layouts are not supported"
}
offset += types.size(field.type, store, selected)
alignment = max(alignment, field_alignment)
}
size = offset
}
size = (size+alignment-1)/alignment*alignment
if u64(record.alignment) > alignment {
return "over-aligned C records are not supported"
}
if u64(record.alignment) < alignment || record.size < size {
return "packed C records are not supported"
}
if record.size != size {
return "non-natural C record layouts are not supported"
}
return ""
}
c_record_by_value_reason :: proc(result: ^cimport.Result, value: cimport.Type_Id, depth := 0) -> string {
if depth > 64 || value == cimport.INVALID_TYPE || int(value) < 0 || int(value) >= len(result.types) {
return ""
}
item := result.types[value]
#partial switch item.kind {
case .Pointer:
return ""
case .Array:
return c_record_by_value_reason(result, item.child, depth+1)
case .Record:
if int(item.record) >= len(result.records) {
return "C record metadata is incomplete"
}
record := result.records[item.record]
if len(record.reason) > 0 {
return record.reason
}
if !record.complete {
return "incomplete C records are pointer-only"
}
}
return ""
}
load_header :: proc(state: ^State, path: string, import_span: source.Span) -> ast.Package_Id {
canonical, ok := filepath.abs(path, state.allocator)
if !ok {
@@ -229,7 +336,7 @@ load_header :: proc(state: ^State, path: string, import_span: source.Span) -> as
name = fmt.tprintf("__c_record_%d", len(state.record_types))
}
record_type = types.named(&state.module.type_store, u32(pkg_id), u32(symbol.intern(state.symbols, name)))
_ = types.define_struct(&state.module.type_store, record_type, nil, true, true)
_ = types.define_record(&state.module.type_store, record_type, nil, true, true, record.kind == .Union)
append(&state.record_identities, strings.clone(record.identity, state.allocator))
append(&state.record_types, record_type)
}
@@ -238,6 +345,42 @@ load_header :: proc(state: ^State, path: string, import_span: source.Span) -> as
type_mapping := make([]types.Type, len(result.types), state.allocator)
defer delete(type_mapping, state.allocator)
for _ in 0..<len(result.records) {
changed := false
for record, record_index in result.records {
if !record.complete || len(record.reason) > 0 {
continue
}
record_type := record_mapping[record_index]
item, ok := types.node(&state.module.type_store, record_type)
if !ok || (item.declared && !item.opaque) {
continue
}
fields := make([]types.Field, len(record.fields), state.allocator)
for field, field_index in record.fields {
fields[field_index] = types.Field{
name=u32(symbol.intern(state.symbols, field.name)),
type=translate_c_type(state, &result, field.type, pkg_id, record_mapping, type_mapping),
offset=field.offset,
}
}
layout_reason := record_layout_reason(&state.module.type_store, record, fields, state.selected)
if len(layout_reason) == 0 {
changed = types.define_record(
&state.module.type_store, record_type, fields, true, false,
record.kind == .Union, record.size, record.alignment,
) || changed
} else if layout_reason != RECORD_DEPENDENCY_PENDING && len(record.reason) == 0 {
delete(result.records[record_index].reason, result.allocator)
result.records[record_index].reason = fmt.aprintf("%s", layout_reason, allocator=result.allocator)
}
delete(fields, state.allocator)
}
if !changed {
break
}
}
for alias in result.aliases {
name := symbol.intern(state.symbols, alias.name)
id := types.named(&state.module.type_store, u32(pkg_id), u32(name))
@@ -263,17 +406,28 @@ load_header :: proc(state: ^State, path: string, import_span: source.Span) -> as
}
function_result := translate_c_type(state, &result, function.result, pkg_id, record_mapping, type_mapping)
unsupported_reason := function.reason
if len(unsupported_reason) == 0 {
for param_type in function.params {
if reason := c_record_by_value_reason(&result, param_type); len(reason) > 0 {
unsupported_reason = reason
break
}
}
}
if len(unsupported_reason) == 0 {
unsupported_reason = c_record_by_value_reason(&result, function.result)
}
if len(unsupported_reason) == 0 {
for param in params {
if types.contains_c_struct_by_value(param.type, &state.module.type_store) {
unsupported_reason = "C records passed by value are not supported"
unsupported_reason = "C record parameter is incomplete or has an unsupported layout"
break
}
}
}
if len(unsupported_reason) == 0 &&
types.contains_c_struct_by_value(function_result, &state.module.type_store) {
unsupported_reason = "C records returned by value are not supported"
unsupported_reason = "C record result is incomplete or has an unsupported layout"
}
name := symbol.intern(state.symbols, function.name)
duplicate := false
@@ -522,7 +676,7 @@ canonical_type :: proc(
mapping[index] = value if !types.is_valid(resolved) else resolved
return mapping[index]
}
if item.kind == .Struct {
if item.kind == .Struct || item.kind == .Union {
mapping[index] = value
fields := types.fields_for(&module.type_store, value)
for &field in fields {
@@ -530,6 +684,16 @@ canonical_type :: proc(
}
return value
}
if item.kind == .Function {
params := make([]types.Type, int(item.field_count), context.temp_allocator)
for param, param_index in types.params_for(&module.type_store, value) {
params[param_index] = canonical_type(module, param.type, mapping, visiting)
}
result := canonical_type(module, item.child, mapping, visiting)
resolved := types.function(&module.type_store, params, result, item.c_abi, item.variadic)
mapping[index] = resolved
return resolved
}
if types.is_valid(item.child) {
item.child = canonical_type(module, item.child, mapping, visiting)
}