
Most of these errors are actually "todo" or "unimplemented" errors, so the return type is known. This means that compilation can proceed (with errors) even though the output will be incorrect. This is useful because this way, all errors in a compilation unit can be shown together to the user.
125 строки
4,6 КиБ
Go
125 строки
4,6 КиБ
Go
package compiler
|
|
|
|
// This file emits the correct map intrinsics for map operations.
|
|
|
|
import (
|
|
"go/token"
|
|
"go/types"
|
|
|
|
"tinygo.org/x/go-llvm"
|
|
)
|
|
|
|
func (c *Compiler) emitMapLookup(keyType, valueType types.Type, m, key llvm.Value, commaOk bool, pos token.Pos) (llvm.Value, error) {
|
|
llvmValueType := c.getLLVMType(valueType)
|
|
mapValueAlloca := c.builder.CreateAlloca(llvmValueType, "hashmap.value")
|
|
mapValuePtr := c.builder.CreateBitCast(mapValueAlloca, c.i8ptrType, "hashmap.valueptr")
|
|
var commaOkValue llvm.Value
|
|
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
|
|
// key is a string
|
|
params := []llvm.Value{m, key, mapValuePtr}
|
|
commaOkValue = c.createRuntimeCall("hashmapStringGet", params, "")
|
|
} else if hashmapIsBinaryKey(keyType) {
|
|
// key can be compared with runtime.memequal
|
|
keyAlloca := c.builder.CreateAlloca(key.Type(), "hashmap.key")
|
|
c.builder.CreateStore(key, keyAlloca)
|
|
keyPtr := c.builder.CreateBitCast(keyAlloca, c.i8ptrType, "hashmap.keyptr")
|
|
params := []llvm.Value{m, keyPtr, mapValuePtr}
|
|
commaOkValue = c.createRuntimeCall("hashmapBinaryGet", params, "")
|
|
} else {
|
|
return llvm.Value{}, c.makeError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String())
|
|
}
|
|
mapValue := c.builder.CreateLoad(mapValueAlloca, "")
|
|
if commaOk {
|
|
tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{llvmValueType, c.ctx.Int1Type()}, false))
|
|
tuple = c.builder.CreateInsertValue(tuple, mapValue, 0, "")
|
|
tuple = c.builder.CreateInsertValue(tuple, commaOkValue, 1, "")
|
|
return tuple, nil
|
|
} else {
|
|
return mapValue, nil
|
|
}
|
|
}
|
|
|
|
func (c *Compiler) emitMapUpdate(keyType types.Type, m, key, value llvm.Value, pos token.Pos) {
|
|
valueAlloca := c.builder.CreateAlloca(value.Type(), "hashmap.value")
|
|
c.builder.CreateStore(value, valueAlloca)
|
|
valuePtr := c.builder.CreateBitCast(valueAlloca, c.i8ptrType, "hashmap.valueptr")
|
|
keyType = keyType.Underlying()
|
|
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
|
|
// key is a string
|
|
params := []llvm.Value{m, key, valuePtr}
|
|
c.createRuntimeCall("hashmapStringSet", params, "")
|
|
} else if hashmapIsBinaryKey(keyType) {
|
|
// key can be compared with runtime.memequal
|
|
keyAlloca := c.builder.CreateAlloca(key.Type(), "hashmap.key")
|
|
c.builder.CreateStore(key, keyAlloca)
|
|
keyPtr := c.builder.CreateBitCast(keyAlloca, c.i8ptrType, "hashmap.keyptr")
|
|
params := []llvm.Value{m, keyPtr, valuePtr}
|
|
c.createRuntimeCall("hashmapBinarySet", params, "")
|
|
} else {
|
|
c.addError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String())
|
|
}
|
|
}
|
|
|
|
func (c *Compiler) emitMapDelete(keyType types.Type, m, key llvm.Value, pos token.Pos) error {
|
|
keyType = keyType.Underlying()
|
|
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
|
|
// key is a string
|
|
params := []llvm.Value{m, key}
|
|
c.createRuntimeCall("hashmapStringDelete", params, "")
|
|
return nil
|
|
} else if hashmapIsBinaryKey(keyType) {
|
|
keyAlloca := c.builder.CreateAlloca(key.Type(), "hashmap.key")
|
|
c.builder.CreateStore(key, keyAlloca)
|
|
keyPtr := c.builder.CreateBitCast(keyAlloca, c.i8ptrType, "hashmap.keyptr")
|
|
params := []llvm.Value{m, keyPtr}
|
|
c.createRuntimeCall("hashmapBinaryDelete", params, "")
|
|
return nil
|
|
} else {
|
|
return c.makeError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String())
|
|
}
|
|
}
|
|
|
|
// Get FNV-1a hash of this string.
|
|
//
|
|
// https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function#FNV-1a_hash
|
|
func hashmapHash(data []byte) uint32 {
|
|
var result uint32 = 2166136261 // FNV offset basis
|
|
for _, c := range data {
|
|
result ^= uint32(c)
|
|
result *= 16777619 // FNV prime
|
|
}
|
|
return result
|
|
}
|
|
|
|
// Get the topmost 8 bits of the hash, without using a special value (like 0).
|
|
func hashmapTopHash(hash uint32) uint8 {
|
|
tophash := uint8(hash >> 24)
|
|
if tophash < 1 {
|
|
// 0 means empty slot, so make it bigger.
|
|
tophash += 1
|
|
}
|
|
return tophash
|
|
}
|
|
|
|
// Returns true if this key type does not contain strings, interfaces etc., so
|
|
// can be compared with runtime.memequal.
|
|
func hashmapIsBinaryKey(keyType types.Type) bool {
|
|
switch keyType := keyType.(type) {
|
|
case *types.Basic:
|
|
return keyType.Info()&(types.IsBoolean|types.IsInteger) != 0
|
|
case *types.Struct:
|
|
for i := 0; i < keyType.NumFields(); i++ {
|
|
fieldType := keyType.Field(i).Type().Underlying()
|
|
if !hashmapIsBinaryKey(fieldType) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
case *types.Array:
|
|
return hashmapIsBinaryKey(keyType.Elem())
|
|
case *types.Named:
|
|
return hashmapIsBinaryKey(keyType.Underlying())
|
|
default:
|
|
return false
|
|
}
|
|
}
|