prune dead specializations

This commit is contained in:
2026-06-12 17:27:47 +02:00
parent 4112b79c6b
commit 66e41e1d9a
4 changed files with 209 additions and 26 deletions
+97 -25
View File
@@ -510,16 +510,28 @@ find_infer_local :: proc(locals: []Infer_Local, name: symbol.Id) -> types.Type {
return types.INVALID
}
spec_signature_equal :: proc(spec: Spec, template: ast.Function_Id, args: []types.Type) -> bool {
if spec.template != template || len(spec.args) != len(args) {
return false
}
for arg, index in args {
if !types.equal(spec.args[index], arg) {
return false
find_spec :: proc(checker: ^Checker, template: ast.Function_Id, actual_args: []types.Type) -> Spec_Id {
function := checker.ast_module.functions[template]
for spec, index in checker.specs {
if spec.template != template || len(spec.args) != len(function.params) {
continue
}
matches := true
for param, param_index in function.params {
actual := types.INVALID
if param_index < len(actual_args) {
actual = actual_args[param_index]
}
if !types.equal(spec.args[param_index], specialized_param_type(param.type, actual)) {
matches = false
break
}
}
if matches {
return spec_id(index)
}
}
return true
return INVALID_SPEC
}
specialized_param_type :: proc(syntax: ast.Type_Syntax, actual: types.Type) -> types.Type {
@@ -544,6 +556,9 @@ can_specialize :: proc(function: ast.Function, actual_args: []types.Type) -> boo
}
ensure_spec :: proc(checker: ^Checker, template: ast.Function_Id, actual_args: []types.Type) -> Spec_Id {
if existing := find_spec(checker, template, actual_args); existing != INVALID_SPEC {
return existing
}
function := checker.ast_module.functions[template]
signature: [dynamic]types.Type
signature.allocator = checker.allocator
@@ -554,12 +569,6 @@ ensure_spec :: proc(checker: ^Checker, template: ast.Function_Id, actual_args: [
}
append(&signature, specialized_param_type(param.type, actual))
}
for spec, index in checker.specs {
if spec_signature_equal(spec, template, signature[:]) {
delete(signature)
return spec_id(index)
}
}
result := type_from_syntax(function.result)
if function.pkg == 0 && function.name == checker.main_symbol && function.result == .Int {
result = types.I32
@@ -572,6 +581,14 @@ ensure_spec :: proc(checker: ^Checker, template: ast.Function_Id, actual_args: [
return index
}
mark_spec_demanded :: proc(checker: ^Checker, id: Spec_Id, stack: ^[dynamic]Spec_Id) {
if id == INVALID_SPEC || checker.specs[id].hir_id != hir.INVALID_FUNCTION {
return
}
checker.specs[id].hir_id = hir.Function_Id(0)
append(stack, id)
}
Infer_Frame :: struct {
expr: ast.Expr_Id,
stage: u8,
@@ -587,6 +604,7 @@ infer_expr :: proc(
locals: []Infer_Local,
pkg := ast.Package_Id(0),
file := ast.File_Id(0),
demanded: ^[dynamic]Spec_Id = nil,
) -> types.Type {
stack := checker.infer_stack
clear_dynamic_array(&stack)
@@ -694,8 +712,19 @@ infer_expr :: proc(
}
function := checker.ast_module.functions[frame.template]
if can_specialize(function, stack[frame_index].args) {
spec := ensure_spec(checker, frame.template, stack[frame_index].args)
last = checker.specs[spec].result
spec := INVALID_SPEC
if demanded == nil {
spec = ensure_spec(checker, frame.template, stack[frame_index].args)
} else if len(expr.args) == len(function.params) {
spec = find_spec(checker, frame.template, stack[frame_index].args)
mark_spec_demanded(checker, spec, demanded)
}
if spec != INVALID_SPEC {
last = checker.specs[spec].result
} else {
declared := type_from_syntax(function.result)
last = declared if declared.kind == .Concrete || declared.kind == .Void else types.INVALID
}
} else {
declared := type_from_syntax(function.result)
if function.pkg == 0 && function.name == checker.main_symbol && function.result == .Int {
@@ -712,7 +741,7 @@ infer_expr :: proc(
return last
}
infer_spec_result :: proc(checker: ^Checker, id: Spec_Id) -> types.Type {
infer_spec_result :: proc(checker: ^Checker, id: Spec_Id, demanded: ^[dynamic]Spec_Id = nil) -> types.Type {
spec := checker.specs[id]
function := checker.ast_module.functions[spec.template]
declared := type_from_syntax(function.result)
@@ -736,17 +765,17 @@ infer_spec_result :: proc(checker: ^Checker, id: Spec_Id) -> types.Type {
statement := checker.ast_module.statements[statement_id]
#partial switch statement.kind {
case .Declaration:
value_type := infer_expr(checker, statement.expr, locals[:], function.pkg, function.file)
value_type := infer_expr(checker, statement.expr, locals[:], function.pkg, function.file, demanded)
declared_local := type_from_syntax(statement.type)
if declared_local.kind == .Concrete {
value_type = declared_local
}
append(&locals, Infer_Local{name = statement.name, type = value_type})
case .Assignment, .Expression:
_ = infer_expr(checker, statement.expr, locals[:], function.pkg, function.file)
_ = infer_expr(checker, statement.expr, locals[:], function.pkg, function.file, demanded)
case .Return:
if statement.expr != ast.INVALID_EXPR {
returned := infer_expr(checker, statement.expr, locals[:], function.pkg, function.file)
returned := infer_expr(checker, statement.expr, locals[:], function.pkg, function.file, demanded)
if !types.is_valid(result) {
result = returned
} else {
@@ -794,10 +823,10 @@ infer_all :: proc(checker: ^Checker) {
changed := false
spec_count := len(checker.specs)
for global, index in checker.ast_module.globals {
inferred := infer_expr(checker, global.expr, nil, global.pkg, global.file)
if type_from_syntax(global.type).kind == .Concrete {
continue
}
inferred := infer_expr(checker, global.expr, nil, global.pkg, global.file)
changed = merge_inferred_type(&checker.global_types[index], inferred) || changed
}
for index := 0; index < len(checker.specs); index += 1 {
@@ -814,6 +843,38 @@ infer_all :: proc(checker: ^Checker) {
}
}
prune_specs :: proc(checker: ^Checker) {
stack: [dynamic]Spec_Id
stack.allocator = checker.allocator
defer delete(stack)
main_template := find_template(checker, checker.main_symbol, 0)
if main_template != ast.INVALID_FUNCTION {
mark_spec_demanded(checker, find_spec(checker, main_template, nil), &stack)
}
for global in checker.ast_module.globals {
_ = infer_expr(checker, global.expr, nil, global.pkg, global.file, &stack)
}
for len(stack) > 0 {
id := pop(&stack)
_ = infer_spec_result(checker, id, &stack)
}
retained := 0
for spec in checker.specs {
if spec.hir_id == hir.INVALID_FUNCTION {
delete(spec.args, checker.allocator)
continue
}
checker.specs[retained] = spec
checker.specs[retained].hir_id = hir.function_id(retained)
retained += 1
}
for len(checker.specs) > retained {
_ = pop(&checker.specs)
}
}
add_hir_expr :: proc(checker: ^Checker, expr: hir.Expr) -> hir.Expr_Id {
id := hir.expr_id(len(checker.module.exprs))
append(&checker.module.exprs, expr)
@@ -1125,9 +1186,22 @@ build_expr :: proc(
continue
}
}
spec := ensure_spec(checker, frame.template, stack[frame_index].arg_types)
spec := find_spec(checker, frame.template, stack[frame_index].arg_types)
delete(stack[frame_index].arg_types, checker.allocator)
stack[frame_index].arg_types = nil
if spec == INVALID_SPEC {
id := source.addf(
checker.diagnostics,
expr.span,
"could not resolve specialization of '%s'",
symbol_text(checker, expr.name),
)
delete(stack[frame_index].built_args, checker.allocator)
stack[frame_index].built_args = nil
last = invalid_hir_expr(checker, expr.span, id)
_ = pop(&stack)
continue
}
for _, index in stack[frame_index].built_args {
stack[frame_index].built_args[index] = coerce_expr(
checker,
@@ -1997,9 +2071,7 @@ check :: proc(
validate_declarations(&checker)
infer_all(&checker)
for index in 0..<len(checker.specs) {
checker.specs[index].hir_id = hir.function_id(index)
}
prune_specs(&checker)
build_globals(&checker)
for index := 0; index < len(checker.specs); index += 1 {
build_function(&checker, spec_id(index))