From 4d2a6d2bbeef8db2a2e9714f3d91372f89bc9b80 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sun, 30 Apr 2023 17:54:03 +0200 Subject: [PATCH] wasm: remove i64 workaround, use BigInt instead Browsers previously didn't support the WebAssembly i64 type, so we had to work around that limitation by converting the LLVM i64 type to something else. Some people used a pair of i32 values, but we used a pointer to a stack allocated i64. Now however, all major browsers and Node.js do support WebAssembly BigInt integration so that i64 values can be passed back and forth between WebAssembly and JavaScript easily. Therefore, I think the time has come to drop support for this workaround. For more information: https://v8.dev/features/wasm-bigint (note that TinyGo has used a slightly different way of passing i64 values between JS and Wasm). For information on browser support: https://webassembly.org/roadmap/ --- builder/build.go | 11 -- compileopts/config.go | 5 - compileopts/target.go | 1 - targets/wasi.json | 3 +- targets/wasm.json | 3 +- targets/wasm_exec.js | 150 ++++++++++++-------------- transform/testdata/wasm-abi.ll | 28 ----- transform/testdata/wasm-abi.out.ll | 45 -------- transform/wasm-abi.go | 167 ----------------------------- transform/wasm-abi_test.go | 19 ---- 10 files changed, 72 insertions(+), 360 deletions(-) delete mode 100644 transform/testdata/wasm-abi.ll delete mode 100644 transform/testdata/wasm-abi.out.ll delete mode 100644 transform/wasm-abi.go delete mode 100644 transform/wasm-abi_test.go diff --git a/builder/build.go b/builder/build.go index 516b9e64..2e8577e3 100644 --- a/builder/build.go +++ b/builder/build.go @@ -1054,17 +1054,6 @@ func optimizeProgram(mod llvm.Module, config *compileopts.Config) error { return err } - // Browsers cannot handle external functions that have type i64 because it - // cannot be represented exactly in JavaScript (JS only has doubles). To - // keep functions interoperable, pass int64 types as pointers to - // stack-allocated values. - if config.WasmAbi() == "js" { - err := transform.ExternalInt64AsPtr(mod, config) - if err != nil { - return err - } - } - // Optimization levels here are roughly the same as Clang, but probably not // exactly. optLevel, sizeLevel, inlinerThreshold := config.OptLevels() diff --git a/compileopts/config.go b/compileopts/config.go index 9a4bc310..39fc4f2a 100644 --- a/compileopts/config.go +++ b/compileopts/config.go @@ -492,11 +492,6 @@ func (c *Config) RelocationModel() string { return "static" } -// WasmAbi returns the WASM ABI which is specified in the target JSON file. -func (c *Config) WasmAbi() string { - return c.Target.WasmAbi -} - // EmulatorName is a shorthand to get the command for this emulator, something // like qemu-system-arm or simavr. func (c *Config) EmulatorName() string { diff --git a/compileopts/target.go b/compileopts/target.go index 40a1d445..3040f902 100644 --- a/compileopts/target.go +++ b/compileopts/target.go @@ -62,7 +62,6 @@ type TargetSpec struct { JLinkDevice string `json:"jlink-device"` CodeModel string `json:"code-model"` RelocationModel string `json:"relocation-model"` - WasmAbi string `json:"wasm-abi"` } // overrideProperties overrides all properties that are set in child into itself using reflection. diff --git a/targets/wasi.json b/targets/wasi.json index 4c43193f..f55c7c1f 100644 --- a/targets/wasi.json +++ b/targets/wasi.json @@ -21,6 +21,5 @@ "extra-files": [ "src/runtime/asm_tinygowasm.S" ], - "emulator": "wasmtime --mapdir=/tmp::{tmpDir} {}", - "wasm-abi": "generic" + "emulator": "wasmtime --mapdir=/tmp::{tmpDir} {}" } diff --git a/targets/wasm.json b/targets/wasm.json index 26494cc4..37034c6c 100644 --- a/targets/wasm.json +++ b/targets/wasm.json @@ -22,6 +22,5 @@ "extra-files": [ "src/runtime/asm_tinygowasm.S" ], - "emulator": "node {root}/targets/wasm_exec.js {}", - "wasm-abi": "js" + "emulator": "node {root}/targets/wasm_exec.js {}" } diff --git a/targets/wasm_exec.js b/targets/wasm_exec.js index 8021b44e..8e29d626 100644 --- a/targets/wasm_exec.js +++ b/targets/wasm_exec.js @@ -130,6 +130,7 @@ const encoder = new TextEncoder("utf-8"); const decoder = new TextDecoder("utf-8"); + let reinterpretBuf = new DataView(new ArrayBuffer(8)); var logLine = []; global.Go = class { @@ -142,19 +143,9 @@ return new DataView(this._inst.exports.memory.buffer); } - const setInt64 = (addr, v) => { - mem().setUint32(addr + 0, v, true); - mem().setUint32(addr + 4, Math.floor(v / 4294967296), true); - } - - const getInt64 = (addr) => { - const low = mem().getUint32(addr + 0, true); - const high = mem().getInt32(addr + 4, true); - return low + high * 4294967296; - } - - const loadValue = (addr) => { - const f = mem().getFloat64(addr, true); + const unboxValue = (v_ref) => { + reinterpretBuf.setBigInt64(0, v_ref, true); + const f = reinterpretBuf.getFloat64(0, true); if (f === 0) { return undefined; } @@ -162,71 +153,70 @@ return f; } - const id = mem().getUint32(addr, true); + const id = v_ref & 0xffffffffn; return this._values[id]; } - const storeValue = (addr, v) => { - const nanHead = 0x7FF80000; + + const loadValue = (addr) => { + let v_ref = mem().getBigUint64(addr, true); + return unboxValue(v_ref); + } + + const boxValue = (v) => { + const nanHead = 0x7FF80000n; if (typeof v === "number") { if (isNaN(v)) { - mem().setUint32(addr + 4, nanHead, true); - mem().setUint32(addr, 0, true); - return; + return nanHead << 32n; } if (v === 0) { - mem().setUint32(addr + 4, nanHead, true); - mem().setUint32(addr, 1, true); - return; + return (nanHead << 32n) | 1n; } - mem().setFloat64(addr, v, true); - return; + reinterpretBuf.setFloat64(0, v, true); + return reinterpretBuf.getBigInt64(0, true); } switch (v) { case undefined: - mem().setFloat64(addr, 0, true); - return; + return 0n; case null: - mem().setUint32(addr + 4, nanHead, true); - mem().setUint32(addr, 2, true); - return; + return (nanHead << 32n) | 2n; case true: - mem().setUint32(addr + 4, nanHead, true); - mem().setUint32(addr, 3, true); - return; + return (nanHead << 32n) | 3n; case false: - mem().setUint32(addr + 4, nanHead, true); - mem().setUint32(addr, 4, true); - return; + return (nanHead << 32n) | 4n; } let id = this._ids.get(v); if (id === undefined) { id = this._idPool.pop(); if (id === undefined) { - id = this._values.length; + id = BigInt(this._values.length); } this._values[id] = v; this._goRefCounts[id] = 0; this._ids.set(v, id); } this._goRefCounts[id]++; - let typeFlag = 1; + let typeFlag = 1n; switch (typeof v) { case "string": - typeFlag = 2; + typeFlag = 2n; break; case "symbol": - typeFlag = 3; + typeFlag = 3n; break; case "function": - typeFlag = 4; + typeFlag = 4n; break; } - mem().setUint32(addr + 4, nanHead | typeFlag, true); - mem().setUint32(addr, id, true); + return id | ((nanHead | typeFlag) << 32n); + } + + const storeValue = (addr, v) => { + let v_ref = boxValue(v); + mem().setBigUint64(addr, v_ref, true); } const loadSlice = (array, len, cap) => { @@ -307,54 +297,54 @@ }, // func finalizeRef(v ref) - "syscall/js.finalizeRef": (sp) => { + "syscall/js.finalizeRef": (v_ref) => { // Note: TinyGo does not support finalizers so this should never be // called. console.error('syscall/js.finalizeRef not implemented'); }, // func stringVal(value string) ref - "syscall/js.stringVal": (ret_ptr, value_ptr, value_len) => { + "syscall/js.stringVal": (value_ptr, value_len) => { const s = loadString(value_ptr, value_len); - storeValue(ret_ptr, s); + return boxValue(s); }, // func valueGet(v ref, p string) ref - "syscall/js.valueGet": (retval, v_addr, p_ptr, p_len) => { + "syscall/js.valueGet": (v_ref, p_ptr, p_len) => { let prop = loadString(p_ptr, p_len); - let value = loadValue(v_addr); - let result = Reflect.get(value, prop); - storeValue(retval, result); + let v = unboxValue(v_ref); + let result = Reflect.get(v, prop); + return boxValue(result); }, // func valueSet(v ref, p string, x ref) - "syscall/js.valueSet": (v_addr, p_ptr, p_len, x_addr) => { - const v = loadValue(v_addr); + "syscall/js.valueSet": (v_ref, p_ptr, p_len, x_ref) => { + const v = unboxValue(v_ref); const p = loadString(p_ptr, p_len); - const x = loadValue(x_addr); + const x = unboxValue(x_ref); Reflect.set(v, p, x); }, // func valueDelete(v ref, p string) - "syscall/js.valueDelete": (v_addr, p_ptr, p_len) => { - const v = loadValue(v_addr); + "syscall/js.valueDelete": (v_ref, p_ptr, p_len) => { + const v = unboxValue(v_ref); const p = loadString(p_ptr, p_len); Reflect.deleteProperty(v, p); }, // func valueIndex(v ref, i int) ref - "syscall/js.valueIndex": (ret_addr, v_addr, i) => { - storeValue(ret_addr, Reflect.get(loadValue(v_addr), i)); + "syscall/js.valueIndex": (v_ref, i) => { + return boxValue(Reflect.get(unboxValue(v_ref), i)); }, // valueSetIndex(v ref, i int, x ref) - "syscall/js.valueSetIndex": (v_addr, i, x_addr) => { - Reflect.set(loadValue(v_addr), i, loadValue(x_addr)); + "syscall/js.valueSetIndex": (v_ref, i, x_ref) => { + Reflect.set(unboxValue(v_ref), i, unboxValue(x_ref)); }, // func valueCall(v ref, m string, args []ref) (ref, bool) - "syscall/js.valueCall": (ret_addr, v_addr, m_ptr, m_len, args_ptr, args_len, args_cap) => { - const v = loadValue(v_addr); + "syscall/js.valueCall": (ret_addr, v_ref, m_ptr, m_len, args_ptr, args_len, args_cap) => { + const v = unboxValue(v_ref); const name = loadString(m_ptr, m_len); const args = loadSliceOfValues(args_ptr, args_len, args_cap); try { @@ -368,9 +358,9 @@ }, // func valueInvoke(v ref, args []ref) (ref, bool) - "syscall/js.valueInvoke": (ret_addr, v_addr, args_ptr, args_len, args_cap) => { + "syscall/js.valueInvoke": (ret_addr, v_ref, args_ptr, args_len, args_cap) => { try { - const v = loadValue(v_addr); + const v = unboxValue(v_ref); const args = loadSliceOfValues(args_ptr, args_len, args_cap); storeValue(ret_addr, Reflect.apply(v, undefined, args)); mem().setUint8(ret_addr + 8, 1); @@ -381,8 +371,8 @@ }, // func valueNew(v ref, args []ref) (ref, bool) - "syscall/js.valueNew": (ret_addr, v_addr, args_ptr, args_len, args_cap) => { - const v = loadValue(v_addr); + "syscall/js.valueNew": (ret_addr, v_ref, args_ptr, args_len, args_cap) => { + const v = unboxValue(v_ref); const args = loadSliceOfValues(args_ptr, args_len, args_cap); try { storeValue(ret_addr, Reflect.construct(v, args)); @@ -394,62 +384,62 @@ }, // func valueLength(v ref) int - "syscall/js.valueLength": (v_addr) => { - return loadValue(v_addr).length; + "syscall/js.valueLength": (v_ref) => { + return unboxValue(v_ref).length; }, // valuePrepareString(v ref) (ref, int) - "syscall/js.valuePrepareString": (ret_addr, v_addr) => { - const s = String(loadValue(v_addr)); + "syscall/js.valuePrepareString": (ret_addr, v_ref) => { + const s = String(unboxValue(v_ref)); const str = encoder.encode(s); storeValue(ret_addr, str); - setInt64(ret_addr + 8, str.length); + mem().setInt32(ret_addr + 8, str.length, true); }, // valueLoadString(v ref, b []byte) - "syscall/js.valueLoadString": (v_addr, slice_ptr, slice_len, slice_cap) => { - const str = loadValue(v_addr); + "syscall/js.valueLoadString": (v_ref, slice_ptr, slice_len, slice_cap) => { + const str = unboxValue(v_ref); loadSlice(slice_ptr, slice_len, slice_cap).set(str); }, // func valueInstanceOf(v ref, t ref) bool - "syscall/js.valueInstanceOf": (v_addr, t_addr) => { - return loadValue(v_addr) instanceof loadValue(t_addr); + "syscall/js.valueInstanceOf": (v_ref, t_ref) => { + return unboxValue(v_ref) instanceof unboxValue(t_ref); }, // func copyBytesToGo(dst []byte, src ref) (int, bool) - "syscall/js.copyBytesToGo": (ret_addr, dest_addr, dest_len, dest_cap, source_addr) => { + "syscall/js.copyBytesToGo": (ret_addr, dest_addr, dest_len, dest_cap, src_ref) => { let num_bytes_copied_addr = ret_addr; let returned_status_addr = ret_addr + 4; // Address of returned boolean status variable const dst = loadSlice(dest_addr, dest_len); - const src = loadValue(source_addr); + const src = unboxValue(src_ref); if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) { mem().setUint8(returned_status_addr, 0); // Return "not ok" status return; } const toCopy = src.subarray(0, dst.length); dst.set(toCopy); - setInt64(num_bytes_copied_addr, toCopy.length); + mem().setUint32(num_bytes_copied_addr, toCopy.length, true); mem().setUint8(returned_status_addr, 1); // Return "ok" status }, // copyBytesToJS(dst ref, src []byte) (int, bool) // Originally copied from upstream Go project, then modified: // https://github.com/golang/go/blob/3f995c3f3b43033013013e6c7ccc93a9b1411ca9/misc/wasm/wasm_exec.js#L404-L416 - "syscall/js.copyBytesToJS": (ret_addr, dest_addr, source_addr, source_len, source_cap) => { + "syscall/js.copyBytesToJS": (ret_addr, dst_ref, src_addr, src_len, src_cap) => { let num_bytes_copied_addr = ret_addr; let returned_status_addr = ret_addr + 4; // Address of returned boolean status variable - const dst = loadValue(dest_addr); - const src = loadSlice(source_addr, source_len); + const dst = unboxValue(dst_ref); + const src = loadSlice(src_addr, src_len); if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) { mem().setUint8(returned_status_addr, 0); // Return "not ok" status return; } const toCopy = src.subarray(0, dst.length); dst.set(toCopy); - setInt64(num_bytes_copied_addr, toCopy.length); + mem().setUint32(num_bytes_copied_addr, toCopy.length, true); mem().setUint8(returned_status_addr, 1); // Return "ok" status }, } diff --git a/transform/testdata/wasm-abi.ll b/transform/testdata/wasm-abi.ll deleted file mode 100644 index ade4b5af..00000000 --- a/transform/testdata/wasm-abi.ll +++ /dev/null @@ -1,28 +0,0 @@ -target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" -target triple = "wasm32-unknown-unknown-wasm" - -declare i64 @externalCall(ptr, i32, i64) - -define internal i64 @testCall(ptr %ptr, i32 %len, i64 %foo) { - %val = call i64 @externalCall(ptr %ptr, i32 %len, i64 %foo) - ret i64 %val -} - -define internal i64 @testCallNonEntry(ptr %ptr, i32 %len) { -entry: - br label %bb1 - -bb1: - %val = call i64 @externalCall(ptr %ptr, i32 %len, i64 3) - ret i64 %val -} - -define void @exportedFunction(i64 %foo) { - %unused = shl i64 %foo, 1 - ret void -} - -define internal void @callExportedFunction(i64 %foo) { - call void @exportedFunction(i64 %foo) - ret void -} diff --git a/transform/testdata/wasm-abi.out.ll b/transform/testdata/wasm-abi.out.ll deleted file mode 100644 index a1fc7d6a..00000000 --- a/transform/testdata/wasm-abi.out.ll +++ /dev/null @@ -1,45 +0,0 @@ -target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" -target triple = "wasm32-unknown-unknown-wasm" - -declare i64 @"externalCall$i64wrap"(ptr, i32, i64) - -define internal i64 @testCall(ptr %ptr, i32 %len, i64 %foo) { - %i64asptr = alloca i64, align 8 - %i64asptr1 = alloca i64, align 8 - store i64 %foo, ptr %i64asptr1, align 8 - call void @externalCall(ptr %i64asptr, ptr %ptr, i32 %len, ptr %i64asptr1) - %retval = load i64, ptr %i64asptr, align 8 - ret i64 %retval -} - -define internal i64 @testCallNonEntry(ptr %ptr, i32 %len) { -entry: - %i64asptr = alloca i64, align 8 - %i64asptr1 = alloca i64, align 8 - br label %bb1 - -bb1: ; preds = %entry - store i64 3, ptr %i64asptr1, align 8 - call void @externalCall(ptr %i64asptr, ptr %ptr, i32 %len, ptr %i64asptr1) - %retval = load i64, ptr %i64asptr, align 8 - ret i64 %retval -} - -define internal void @"exportedFunction$i64wrap"(i64 %foo) unnamed_addr { - %unused = shl i64 %foo, 1 - ret void -} - -define internal void @callExportedFunction(i64 %foo) { - call void @"exportedFunction$i64wrap"(i64 %foo) - ret void -} - -declare void @externalCall(ptr, ptr, i32, ptr) - -define void @exportedFunction(ptr %0) { -entry: - %i64 = load i64, ptr %0, align 8 - call void @"exportedFunction$i64wrap"(i64 %i64) - ret void -} diff --git a/transform/wasm-abi.go b/transform/wasm-abi.go deleted file mode 100644 index 081558c9..00000000 --- a/transform/wasm-abi.go +++ /dev/null @@ -1,167 +0,0 @@ -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 -} diff --git a/transform/wasm-abi_test.go b/transform/wasm-abi_test.go deleted file mode 100644 index 374bba13..00000000 --- a/transform/wasm-abi_test.go +++ /dev/null @@ -1,19 +0,0 @@ -package transform_test - -import ( - "testing" - - "github.com/tinygo-org/tinygo/transform" - "tinygo.org/x/go-llvm" -) - -func TestWasmABI(t *testing.T) { - t.Parallel() - testTransform(t, "testdata/wasm-abi", func(mod llvm.Module) { - // Run ABI change pass. - err := transform.ExternalInt64AsPtr(mod, defaultTestConfig) - if err != nil { - t.Errorf("failed to change wasm ABI: %v", err) - } - }) -}