|
|
|
package transform
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/tinygo-org/tinygo/compileopts"
|
|
|
|
"tinygo.org/x/go-llvm"
|
|
|
|
)
|
|
|
|
|
|
|
|
// ExternalInt64AsPtr converts i64 parameters in externally-visible functions to
|
|
|
|
// values passed by reference (*i64), to work around the lack of 64-bit integers
|
|
|
|
// in JavaScript (commonly used together with WebAssembly). Once that's
|
|
|
|
// resolved, this pass may be avoided. For more details:
|
|
|
|
// https://github.com/WebAssembly/design/issues/1172
|
|
|
|
//
|
|
|
|
// This pass is enabled via the wasm-abi JSON target key.
|
|
|
|
func ExternalInt64AsPtr(mod llvm.Module, config *compileopts.Config) error {
|
|
|
|
ctx := mod.Context()
|
|
|
|
builder := ctx.NewBuilder()
|
|
|
|
defer builder.Dispose()
|
|
|
|
int64Type := ctx.Int64Type()
|
|
|
|
int64PtrType := llvm.PointerType(int64Type, 0)
|
|
|
|
|
|
|
|
// This builder is only used for creating new allocas in the entry block of
|
|
|
|
// a function, avoiding many SetInsertPoint* calls.
|
|
|
|
entryBlockBuilder := ctx.NewBuilder()
|
|
|
|
defer entryBlockBuilder.Dispose()
|
|
|
|
|
|
|
|
for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
|
|
|
|
if fn.Linkage() != llvm.ExternalLinkage {
|
|
|
|
// Only change externally visible functions (exports and imports).
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if strings.HasPrefix(fn.Name(), "llvm.") || strings.HasPrefix(fn.Name(), "runtime.") {
|
|
|
|
// Do not try to modify the signature of internal LLVM functions and
|
|
|
|
// assume that runtime functions are only temporarily exported for
|
|
|
|
// transforms.
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if !fn.GetStringAttributeAtIndex(-1, "tinygo-methods").IsNil() {
|
|
|
|
// These are internal functions (interface method call, interface
|
|
|
|
// type assert) that will be lowered by the interface lowering pass.
|
|
|
|
// Don't transform them.
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
hasInt64 := false
|
|
|
|
paramTypes := []llvm.Type{}
|
|
|
|
|
|
|
|
// Check return type for 64-bit integer.
|
|
|
|
fnType := fn.GlobalValueType()
|
|
|
|
returnType := fnType.ReturnType()
|
|
|
|
if returnType == int64Type {
|
|
|
|
hasInt64 = true
|
|
|
|
paramTypes = append(paramTypes, int64PtrType)
|
|
|
|
returnType = ctx.VoidType()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check param types for 64-bit integers.
|
|
|
|
for param := fn.FirstParam(); !param.IsNil(); param = llvm.NextParam(param) {
|
|
|
|
if param.Type() == int64Type {
|
|
|
|
hasInt64 = true
|
|
|
|
paramTypes = append(paramTypes, int64PtrType)
|
|
|
|
} else {
|
|
|
|
paramTypes = append(paramTypes, param.Type())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !hasInt64 {
|
|
|
|
// No i64 in the paramter list.
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add $i64wrapper to the real function name as it is only used
|
|
|
|
// internally.
|
|
|
|
// Add a new function with the correct signature that is exported.
|
|
|
|
name := fn.Name()
|
|
|
|
fn.SetName(name + "$i64wrap")
|
|
|
|
externalFnType := llvm.FunctionType(returnType, paramTypes, fnType.IsFunctionVarArg())
|
|
|
|
externalFn := llvm.AddFunction(mod, name, externalFnType)
|
|
|
|
AddStandardAttributes(fn, config)
|
|
|
|
|
|
|
|
if fn.IsDeclaration() {
|
|
|
|
// Just a declaration: the definition doesn't exist on the Go side
|
|
|
|
// so it cannot be called from external code.
|
|
|
|
// Update all users to call the external function.
|
|
|
|
// The old $i64wrapper function could be removed, but it may as well
|
|
|
|
// be left in place.
|
|
|
|
for _, call := range getUses(fn) {
|
|
|
|
entryBlockBuilder.SetInsertPointBefore(call.InstructionParent().Parent().EntryBasicBlock().FirstInstruction())
|
|
|
|
builder.SetInsertPointBefore(call)
|
|
|
|
callParams := []llvm.Value{}
|
|
|
|
var retvalAlloca llvm.Value
|
|
|
|
if fnType.ReturnType() == int64Type {
|
|
|
|
retvalAlloca = entryBlockBuilder.CreateAlloca(int64Type, "i64asptr")
|
|
|
|
callParams = append(callParams, retvalAlloca)
|
|
|
|
}
|
|
|
|
for i := 0; i < call.OperandsCount()-1; i++ {
|
|
|
|
operand := call.Operand(i)
|
|
|
|
if operand.Type() == int64Type {
|
|
|
|
// Pass a stack-allocated pointer instead of the value
|
|
|
|
// itself.
|
|
|
|
alloca := entryBlockBuilder.CreateAlloca(int64Type, "i64asptr")
|
|
|
|
builder.CreateStore(operand, alloca)
|
|
|
|
callParams = append(callParams, alloca)
|
|
|
|
} else {
|
|
|
|
// Unchanged parameter.
|
|
|
|
callParams = append(callParams, operand)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
var callName string
|
|
|
|
if returnType.TypeKind() != llvm.VoidTypeKind {
|
|
|
|
// Only use the name of the old call instruction if the new
|
|
|
|
// call is not a void call.
|
|
|
|
// A call instruction with an i64 return type may have had a
|
|
|
|
// name, but it cannot have a name after this transform
|
|
|
|
// because the return type will now be void.
|
|
|
|
callName = call.Name()
|
|
|
|
}
|
|
|
|
if fnType.ReturnType() == int64Type {
|
|
|
|
// Pass a stack-allocated pointer as the first parameter
|
|
|
|
// where the return value should be stored, instead of using
|
|
|
|
// the regular return value.
|
|
|
|
builder.CreateCall(externalFnType, externalFn, callParams, callName)
|
|
|
|
returnValue := builder.CreateLoad(int64Type, retvalAlloca, "retval")
|
|
|
|
call.ReplaceAllUsesWith(returnValue)
|
|
|
|
call.EraseFromParentAsInstruction()
|
|
|
|
} else {
|
|
|
|
newCall := builder.CreateCall(externalFnType, externalFn, callParams, callName)
|
|
|
|
call.ReplaceAllUsesWith(newCall)
|
|
|
|
call.EraseFromParentAsInstruction()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// The function has a definition in Go. This means that it may still
|
|
|
|
// be called both Go and from external code.
|
|
|
|
// Keep existing calls with the existing convention in place (for
|
|
|
|
// better performance), but export a new wrapper function with the
|
|
|
|
// correct calling convention.
|
|
|
|
fn.SetLinkage(llvm.InternalLinkage)
|
|
|
|
fn.SetUnnamedAddr(true)
|
|
|
|
entryBlock := ctx.AddBasicBlock(externalFn, "entry")
|
|
|
|
builder.SetInsertPointAtEnd(entryBlock)
|
|
|
|
var callParams []llvm.Value
|
|
|
|
if fnType.ReturnType() == int64Type {
|
|
|
|
return errors.New("not yet implemented: exported function returns i64 with the JS wasm-abi; " +
|
|
|
|
"see https://tinygo.org/compiler-internals/calling-convention/")
|
|
|
|
}
|
|
|
|
for i, origParam := range fn.Params() {
|
|
|
|
paramValue := externalFn.Param(i)
|
|
|
|
if origParam.Type() == int64Type {
|
|
|
|
paramValue = builder.CreateLoad(int64Type, paramValue, "i64")
|
|
|
|
}
|
|
|
|
callParams = append(callParams, paramValue)
|
|
|
|
}
|
|
|
|
retval := builder.CreateCall(fn.GlobalValueType(), fn, callParams, "")
|
|
|
|
if retval.Type().TypeKind() == llvm.VoidTypeKind {
|
|
|
|
builder.CreateRetVoid()
|
|
|
|
} else {
|
|
|
|
builder.CreateRet(retval)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|