Assignment coercions are much more IR efficient

This commit is contained in:
Sasha Koshka 2024-01-27 22:45:28 -05:00
parent cfe12634ef
commit 079a05ccca
5 changed files with 138 additions and 127 deletions

View File

@ -7,61 +7,68 @@ import "git.tebibyte.media/sashakoshka/fspl/entity"
import "git.tebibyte.media/sashakoshka/fspl/analyzer"
func (this *generator) generateAssignment (assignment *entity.Assignment) (llvm.Value, error) {
source, err := this.generateAssignmentSource (
assignment.Value,
assignment.Location.Type())
if err != nil { return nil, err }
destination, err := this.generateExpressionLoc(assignment.Location)
if err != nil { return nil, err }
this.blockManager.NewStore(source, destination)
return nil, nil
return this.generateAssignmentToDestination (
assignment.Value,
assignment.Location.Type(),
destination)
}
// generateAssignmentSource performs type coercions if necessary, mainly
// interface assignment. this should be called instead of generateExpression
// when the type to assign to is known.
func (this *generator) generateAssignmentSource (
from entity.Expression,
to entity.Type,
// generateAssignmentToDestination performs type coercions if necessary, mainly
// interface assignment. This should be called when the user is assigning a
// value to something via an assignment statement, a function/method call, or a
// field in a composite literal.
// irDestLoc specifies the location to assign the source to. If it is nil, this
// function will allocate a destination (if necessary) and return its value as a
// register.
func (this *generator) generateAssignmentToDestination (
source entity.Expression,
destType entity.Type,
irDestLoc llvm.Value,
) (
llvm.Value,
error,
) {
toBase := analyzer.ReduceToBase(to)
destinationSpecified := irDestLoc != nil
destTypeBase := analyzer.ReduceToBase(destType)
switch toBase := toBase.(type) {
switch destTypeBase := destTypeBase.(type) {
// conversion from any type to interface
case *entity.TypeInterface:
// methods defined on interface or pointer types cannot pass
// through an interface!
// create destination interface
irToType, err := this.generateType(to)
// create destination interface, if necessary
irDestType, err := this.generateType(destType)
if err != nil { return nil, err }
destination := this.blockManager.newAllocaFront(irToType)
toDataField := this.getInterfaceDataFieldLoc(destination, irToType)
if !destinationSpecified {
irDestLoc = this.blockManager.newAllocaFront(irDestType)
}
toDataField := this.getInterfaceDataFieldLoc(irDestLoc, irDestType)
fromType := from.Type()
switch fromBase := analyzer.ReduceToBase(fromType).(type) {
sourceType := source.Type()
switch sourceTypeBase := analyzer.ReduceToBase(sourceType).(type) {
// conversion from interface to interface
case *entity.TypeInterface:
// re-assign data
source, err := this.generateExpressionLoc(from)
source, err := this.generateExpressionLoc(source)
if err != nil { return nil, err }
irFromType, err := this.generateType(fromType)
irFromType, err := this.generateType(sourceType)
if err != nil { return nil, err }
fromDataFieldAddress := this.getInterfaceDataFieldLoc(source, irFromType)
fromData := this.blockManager.NewLoad(new(llvm.TypePointer), fromDataFieldAddress)
this.blockManager.NewStore(fromData, toDataField)
// re-assign behaviors
for _, name := range toBase.BehaviorOrder {
for _, name := range destTypeBase.BehaviorOrder {
fromBehaviorField := this.getInterfaceBehaviorFieldLoc (
fromBase, source,
sourceTypeBase, source,
irFromType, name)
toBehaviorField := this.getInterfaceBehaviorFieldLoc (
toBase, destination,
irToType, name)
destTypeBase, irDestLoc,
irDestType, name)
fromBehavior := this.blockManager.NewLoad(new(llvm.TypePointer), fromBehaviorField)
this.blockManager.NewStore(fromBehavior, toBehaviorField)
}
@ -69,22 +76,22 @@ func (this *generator) generateAssignmentSource (
// conversion from pointer to interface
case *entity.TypePointer:
// assign data
source, err := this.generateExpressionVal(from)
source, err := this.generateExpressionVal(source)
if err != nil { return nil, err }
fromData := this.blockManager.NewLoad(new(llvm.TypePointer), source)
this.blockManager.NewStore(fromData, toDataField)
fromReferenced, ok := fromBase.Referenced.(*entity.TypeNamed)
fromReferenced, ok := sourceTypeBase.Referenced.(*entity.TypeNamed)
if !ok {
return nil, errors.New(fmt.Sprint(
"can't assign", fromBase, "to interface"))
"can't assign", sourceTypeBase, "to interface"))
}
// assign behaviors
for _, name := range toBase.BehaviorOrder {
for _, name := range destTypeBase.BehaviorOrder {
toBehaviorFieldAddress := this.getInterfaceBehaviorFieldLoc (
toBase, destination,
irToType, name)
destTypeBase, irDestLoc,
irDestType, name)
fromBehavior, err := this.method(fromReferenced.Name, name)
if err != nil { return nil, err }
this.blockManager.NewStore(fromBehavior, toBehaviorFieldAddress)
@ -93,39 +100,66 @@ func (this *generator) generateAssignmentSource (
// conversion from other type to interface
default:
// assign data
source, err := this.generateExpressionLoc(from)
source, err := this.generateExpressionLoc(source)
if err != nil { return nil, err }
this.blockManager.NewStore(source, toDataField)
fromType, ok := fromType.(*entity.TypeNamed)
sourceType, ok := sourceType.(*entity.TypeNamed)
if !ok {
return nil, errors.New(fmt.Sprint(
"can't assign", fromBase, "to interface"))
"can't assign", sourceType, "to interface"))
}
// assign behaviors
for _, name := range toBase.BehaviorOrder {
for _, name := range destTypeBase.BehaviorOrder {
toBehaviorFieldAddress := this.getInterfaceBehaviorFieldLoc (
toBase, destination,
irToType, name)
fromBehavior, err := this.method(fromType.Name, name)
destTypeBase, irDestLoc,
irDestType, name)
fromBehavior, err := this.method(sourceType.Name, name)
if err != nil { return nil, err }
this.blockManager.NewStore(fromBehavior, toBehaviorFieldAddress)
}
}
return this.blockManager.NewLoad(irToType, destination), nil
if destinationSpecified {
return nil, nil
} else {
return this.blockManager.NewLoad(irDestType, irDestLoc), nil
}
// conversion from array to slice
// conversion from any type to slice
case *entity.TypeSlice:
switch fromBase := analyzer.ReduceToBase(from.Type()).(type) {
switch sourceTypeBase := analyzer.ReduceToBase(source.Type()).(type) {
// conversion from array to slice
case *entity.TypeArray:
source, err := this.generateExpressionVal(from)
// TODO:
array, err := this.generateExpressionLoc(source)
if err != nil { return nil, err }
return this.generateSliceDefinedLength(to, source, int64(fromBase.Length))
irDestType, err := this.generateType(destType)
if err != nil { return nil, err }
if !destinationSpecified {
irDestLoc = this.blockManager.newAllocaFront(irDestType)
}
destDataField := this.getSliceDataFieldLoc(irDestLoc, irDestType)
destLengthField := this.getSliceLengthFieldLoc(irDestLoc, irDestType)
this.blockManager.NewStore(array, destDataField)
this.blockManager.NewStore (
llvm.NewConstInt(llvm.I32, int64(sourceTypeBase.Length)),
destLengthField)
if !destinationSpecified {
return this.blockManager.NewLoad(irDestType, irDestLoc), nil
}
}
}
source, err := this.generateExpressionVal(from)
irSource, err := this.generateExpressionVal(source)
if err != nil { return nil, err }
return source, nil
if destinationSpecified {
this.blockManager.NewStore(irDestLoc, irSource)
return nil, nil
} else {
return irSource, nil
}
}

View File

@ -28,13 +28,23 @@ func (this *generator) generateSliceDefinedLength (ty entity.Type, data llvm.Val
}
func (this *generator) getSliceDataAddress (slice llvm.Value, irType llvm.Type) llvm.Value {
dataAddressFieldAddress := this.blockManager.NewGetElementPtr (
return this.blockManager.NewLoad (
new(llvm.TypePointer),
this.getSliceDataFieldLoc(slice, irType))
}
func (this *generator) getSliceDataFieldLoc (slice llvm.Value, irType llvm.Type) llvm.Value {
return this.blockManager.NewGetElementPtr (
irType, slice,
llvm.NewConstInt(llvm.I32, 0),
llvm.NewConstInt(llvm.I32, 0))
return this.blockManager.NewLoad (
new(llvm.TypePointer),
dataAddressFieldAddress)
}
func (this *generator) getSliceLengthFieldLoc (slice llvm.Value, irType llvm.Type) llvm.Value {
return this.blockManager.NewGetElementPtr (
irType, slice,
llvm.NewConstInt(llvm.I32, 0),
llvm.NewConstInt(llvm.I32, 1))
}
func (this *generator) getInterfaceDataFieldLoc (iface llvm.Value, irType llvm.Type) llvm.Value {

View File

@ -12,9 +12,10 @@ func (this *generator) generateCallVal (call *entity.Call) (llvm.Value, error) {
args := make([]llvm.Value, len(call.Arguments))
for index, argument := range call.Arguments {
irArgument, err := this.generateAssignmentSource (
irArgument, err := this.generateAssignmentToDestination (
argument,
call.Function.Signature.Arguments[index].Type())
call.Function.Signature.Arguments[index].Type(),
nil)
if err != nil { return nil, err }
args[index] = irArgument
@ -30,9 +31,10 @@ func (this *generator) generateMethodCallVal (call *entity.MethodCall) (llvm.Val
// build list of args
args := make([]llvm.Value, len(call.Arguments) + 1)
for index, argument := range call.Arguments {
irArgument, err := this.generateAssignmentSource (
irArgument, err := this.generateAssignmentToDestination (
argument,
signature.Arguments[index].Type())
signature.Arguments[index].Type(),
nil)
if err != nil { return nil, err }
args[index + 1] = irArgument
}

View File

@ -194,63 +194,7 @@ File.[write buffer:*:Byte]:Index = [write [.this] [~*Byte buffer] [#buffer]]
func TestInterfaceInStruct (test *testing.T) {
testString (test,
`%Writer = type { ptr, ptr }
%A = type { %Writer }
%File = type i32
%Index = type i64
define void @main() {
0:
%1 = alloca %A
%2 = getelementptr %A, ptr %1, i32 0, i32 0
store i32 0, ptr %2
%3 = load %A, ptr %1
%4 = alloca %A
store %A %3, ptr %4
%5 = alloca %Writer
%6 = getelementptr %Writer, ptr %5, i32 0, i32 0
%7 = getelementptr %A, ptr %4, i32 0, i32 0
%8 = getelementptr %Writer, ptr %7, i32 0, i32 0
%9 = load ptr, ptr %8
store ptr %9, ptr %6
%10 = getelementptr %Writer, ptr %7, i32 0, i32 1
%11 = getelementptr %Writer, ptr %5, i32 0, i32 1
%12 = load ptr, ptr %10
store ptr %12, ptr %11
%13 = load %Writer, ptr %5
%14 = alloca %Writer
store %Writer %13, ptr %14
%15 = alloca [1 x i8]
%16 = getelementptr [1 x i8], ptr %15, i32 0, i32 0
store i8 33, ptr %16
%17 = alloca { ptr, %Index }
%18 = getelementptr { ptr, %Index }, ptr %17, i32 0, i32 0
store ptr %15, ptr %18
%19 = getelementptr { ptr, %Index }, ptr %17, i32 0, i32 1
store %Index 1, ptr %19
%20 = load { ptr, %Index }, ptr %17
%21 = getelementptr %Writer, ptr %14, i32 0, i32 1
%22 = getelementptr %Writer, ptr %14, i32 0, i32 0
%23 = load ptr, ptr %22
%24 = load ptr, ptr %21
%25 = call %Index %24(ptr %23, { ptr, %Index } %20)
ret void
}
declare %Index @write(%File %fd, ptr %buffer, %Index %count)
define %Index @File.write(ptr %this, { ptr, %Index } %buffer) {
0:
%1 = alloca ptr
store ptr %this, ptr %1
%2 = alloca { ptr, %Index }
store { ptr, %Index } %buffer, ptr %2
%3 = load ptr, ptr %1
%4 = load %File, ptr %3
%5 = getelementptr { ptr, %Index }, ptr %2, i32 0, i32 0
%6 = load ptr, ptr %5
%7 = getelementptr { ptr, %Index }, ptr %2, i32 0, i32 1
%8 = load %Index, ptr %7
%9 = call %Index @write(%File %4, ptr %6, %Index %8)
ret %Index %9
}
`
`,
`
Writer: ([write buffer:*:Byte]: Index)
@ -267,3 +211,17 @@ File.[write buffer:*:Byte]:Index = [write [.this] [~*Byte buffer] [#buffer]]
}
`)
}
func TestInterfaceAssignment (test *testing.T) {
testString (test,
`
`,
`
Face: ([x]:Int)
Impl: Int
Impl.[x]:Int = 5
[main] 'main' = {
i:Face = f:Impl
}
`)
}

View File

@ -19,6 +19,7 @@ func (this *generator) generateLiteralFloat (literal *entity.LiteralFloat) (llvm
}
func (this *generator) generateLiteralArrayLoc (literal *entity.LiteralArray) (llvm.Value, error) {
// TODO support irDestLoc here, make use of it in generateAssignmentToDestination
base := analyzer.ReduceToBase(literal.Type())
makeData := func (elementType entity.Type, irArrayType llvm.Type) (llvm.Value, error) {
@ -32,15 +33,15 @@ func (this *generator) generateLiteralArrayLoc (literal *entity.LiteralArray) (l
}
array := this.blockManager.newAllocaFront(irArrayType)
for index, element := range literal.Elements {
irElement, err := this.generateAssignmentSource (
element,
elementType)
if err != nil { return nil, err }
elementPointer := this.blockManager.NewGetElementPtr (
irArrayType, array,
llvm.NewConstInt(llvm.I32, 0),
llvm.NewConstInt(llvm.I32, int64(index)))
this.blockManager.NewStore(irElement, elementPointer)
_, err := this.generateAssignmentToDestination (
element,
elementType,
elementPointer)
if err != nil { return nil, err }
}
return array, nil
}
@ -67,6 +68,7 @@ func (this *generator) generateLiteralArrayLoc (literal *entity.LiteralArray) (l
}
func (this *generator) generateLiteralStringLoc (literal *entity.LiteralString) (llvm.Value, error) {
// TODO support irDestLoc here, make use of it in generateAssignmentToDestination
base := analyzer.ReduceToBase(literal.Type())
makeData := func (anyElementType entity.Type, cstring bool) (llvm.Value, int64, error) {
@ -202,6 +204,8 @@ func (this *generator) generateLiteralStringLoc (literal *entity.LiteralString)
}
func (this *generator) generateLiteralStructLoc (literal *entity.LiteralStruct) (llvm.Value, error) {
// TODO support irDestLoc here, make use of it in generateAssignmentToDestination
base, ok := analyzer.ReduceToBase(literal.Type()).(*entity.TypeStruct)
if !ok { return nil, errors.New(fmt.Sprintln("struct can't be used as ", literal.Type())) }
@ -211,21 +215,24 @@ func (this *generator) generateLiteralStructLoc (literal *entity.LiteralStruct)
structure := this.blockManager.newAllocaFront(irType)
for index, member := range base.Members {
var irMember llvm.Value
if pair, ok := literal.MemberMap[member.Name]; ok {
irMember, err = this.generateExpressionVal(pair.Value)
if err != nil { return nil, err }
} else {
irMemberType, err := this.generateType(member.Type())
if err != nil { return nil, err }
irMember = llvm.NewConstZeroInitializer(irMemberType)
}
elementPointer := this.blockManager.NewGetElementPtr (
irType, structure,
llvm.NewConstInt(llvm.I32, 0),
llvm.NewConstInt(llvm.I32, int64(index)))
this.blockManager.NewStore(irMember, elementPointer)
if pair, ok := literal.MemberMap[member.Name]; ok {
_, err = this.generateAssignmentToDestination (
pair.Value,
member.Type(),
elementPointer)
if err != nil { return nil, err }
} else {
irMemberType, err := this.generateType(member.Type())
if err != nil { return nil, err }
this.blockManager.NewStore (
llvm.NewConstZeroInitializer(irMemberType),
elementPointer)
}
}
return structure, nil