Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 44 additions & 22 deletions internal/backend/llvm/drop_emit.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ func emitDropValue(b *llvmBuilder, value llvmValue, typeID ir.TypeID) {
emitOwnedPointerFree(b, value, typ.Elem)
return
}
if typ.Kind == ir.TypeOptional {
emitOptionalDrop(b, value, typ.Elem)
if typ.Kind == ir.TypeVariant {
emitVariantDrop(b, value, typ)
return
}
if typ.Kind == ir.TypeArray && typ.Length == "" {
Expand Down Expand Up @@ -161,24 +161,34 @@ func emitInterfaceStorageRelease(b *llvmBuilder, interfaceType ir.TypeID, interf
b.call(releaseFn, []llvmValue{allocator, data})
}

func emitOptionalDrop(b *llvmBuilder, value llvmValue, inner ir.TypeID) {
if !typeNeedsDrop(b.emitter.mod.Types, inner) {
func emitVariantDrop(b *llvmBuilder, value llvmValue, variant ir.Type) {
dropCases := make([]int, 0, len(variant.Cases))
for caseIndex, variantCase := range variant.Cases {
if variantCase.Payload != ir.InvalidType && typeNeedsDrop(b.emitter.mod.Types, variantCase.Payload) {
dropCases = append(dropCases, caseIndex)
}
}
if len(dropCases) == 0 {
return
}
present := b.extractField(value, llvmFieldPresent)
payload := b.extractField(value, llvmFieldValue)
emitConditionalDrop(b, present, payload, inner)
}

func emitConditionalDrop(b *llvmBuilder, condition, value llvmValue, typeID ir.TypeID) {
id := b.nextID
b.nextID++
dropLabel := fmt.Sprintf("drop_some_%d", id)
doneLabel := fmt.Sprintf("drop_done_%d", id)
b.condBranch(condition, dropLabel, doneLabel)
b.namedLabel(dropLabel)
emitDropValue(b, value, typeID)
b.branch(doneLabel)
tag := b.variantTag(value)
doneLabel := fmt.Sprintf("drop_variant_done_%d", id)
switchCases := make([]llvmSwitchCase, len(dropCases))
for i, caseIndex := range dropCases {
switchCases[i] = llvmSwitchCase{
Value: b.variantCaseTag(caseIndex, tag.Layout),
Label: fmt.Sprintf("drop_variant_%d_case_%d", id, caseIndex),
}
}
b.switchBranch(tag, doneLabel, switchCases)
for i, caseIndex := range dropCases {
b.namedLabel(switchCases[i].Label)
variantCase := variant.Cases[caseIndex]
emitDropValue(b, b.variantPayload(value, caseIndex), variantCase.Payload)
b.branch(doneLabel)
}
b.namedLabel(doneLabel)
}

Expand Down Expand Up @@ -249,8 +259,12 @@ func typeNeedsDrop(types *ir.TypeTable, id ir.TypeID) bool {
switch typ.Kind {
case ir.TypeOwnedPtr, ir.TypeString:
return true
case ir.TypeOptional:
return typeNeedsDrop(types, typ.Elem)
case ir.TypeVariant:
for _, variantCase := range typ.Cases {
if variantCase.Payload != ir.InvalidType && typeNeedsDrop(types, variantCase.Payload) {
return true
}
}
case ir.TypeArray:
return typ.Length == "" || typeNeedsDrop(types, typ.Elem)
case ir.TypeStruct:
Expand All @@ -273,8 +287,12 @@ func typeCarriesAllocatorID(types *ir.TypeTable, id ir.TypeID) bool {
return true
case ir.TypeOwnedPtr:
return !isInterfaceType(types, typ.Elem)
case ir.TypeOptional:
return typeCarriesAllocatorID(types, typ.Elem)
case ir.TypeVariant:
for _, variantCase := range typ.Cases {
if variantCase.Payload != ir.InvalidType && typeCarriesAllocatorID(types, variantCase.Payload) {
return true
}
}
case ir.TypeArray:
return typ.Length == "" || typeCarriesAllocatorID(types, typ.Elem)
case ir.TypeStruct:
Expand All @@ -295,8 +313,12 @@ func typeNeedsRawFreeID(types *ir.TypeTable, id ir.TypeID) bool {
switch typ.Kind {
case ir.TypeOwnedPtr:
return !isInterfaceType(types, typ.Elem) && typeNeedsRawFreeID(types, typ.Elem)
case ir.TypeOptional:
return typeNeedsRawFreeID(types, typ.Elem)
case ir.TypeVariant:
for _, variantCase := range typ.Cases {
if variantCase.Payload != ir.InvalidType && typeNeedsRawFreeID(types, variantCase.Payload) {
return true
}
}
case ir.TypeArray:
return typ.Length == "" || typeNeedsRawFreeID(types, typ.Elem)
case ir.TypeStruct:
Expand Down
44 changes: 44 additions & 0 deletions internal/backend/llvm/emitter.go
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,8 @@ func GenerateLLVMIR(mod *mir.Module, diag *diagnostics.DiagnosticBag, targetInfo
case *mir.Branch:
cond := emitCondRef(lb, term.Cond)
lb.condBranch(cond, fmt.Sprintf("b%d", term.ThenID), fmt.Sprintf("b%d", term.ElseID))
case *mir.SwitchVariant:
emitVariantSwitch(lb, term)
case *mir.Ret:
if term.Value == nil || isVoidType(mod.Types, fn.ReturnType) {
if returnLayout.Kind != llvmLayoutVoid {
Expand All @@ -336,6 +338,48 @@ func GenerateLLVMIR(mod *mir.Module, diag *diagnostics.DiagnosticBag, targetInfo
return finalLLVMText(&b, emitter)
}

func emitVariantSwitch(b *llvmBuilder, term *mir.SwitchVariant) {
if b == nil || term == nil || term.Value == nil || len(term.Targets) == 0 {
if b != nil {
b.emitter.markInvalid("variant switch requires subject and targets")
}
return
}
variant, ok := b.emitter.mod.Types.Type(mirRefType(term.Value))
if !ok || variant.Kind != ir.TypeVariant {
b.emitter.markInvalid("variant switch requires variant subject")
return
}
if len(term.Targets) != len(variant.Cases) {
b.emitter.markInvalid("variant switch must cover every case")
return
}
value := emitRef(b, term.Value)
tag := b.variantTag(value)
cases := make([]llvmSwitchCase, len(term.Targets))
seen := make(map[int]struct{}, len(term.Targets))
for i, target := range term.Targets {
if _, caseOK := variant.VariantCase(target.Case); !caseOK {
b.emitter.markInvalid(fmt.Sprintf("variant switch has invalid case %d", target.Case))
return
}
if _, duplicate := seen[target.Case]; duplicate {
b.emitter.markInvalid(fmt.Sprintf("variant switch repeats case %d", target.Case))
return
}
seen[target.Case] = struct{}{}
cases[i] = llvmSwitchCase{
Value: b.variantCaseTag(target.Case, tag.Layout),
Label: fmt.Sprintf("b%d", target.TargetID),
}
}
invalidLabel := fmt.Sprintf("invalid_variant_%d", b.nextID)
b.nextID++
b.switchBranch(tag, invalidLabel, cases)
b.namedLabel(invalidLabel)
b.trap()
}

// ValidateRuntimeSymbols checks runtime ABI reservations after ownership and
// lowering have made actual print, allocation, and destruction use explicit.
func ValidateRuntimeSymbols(modules []*mir.Module, diag *diagnostics.DiagnosticBag) bool {
Expand Down
Loading