WebAssembly: Implement call
authorJF Bastien <jfb@google.com>
Mon, 24 Aug 2015 22:16:48 +0000 (22:16 +0000)
committerJF Bastien <jfb@google.com>
Mon, 24 Aug 2015 22:16:48 +0000 (22:16 +0000)
Summary: Support function calls.

Reviewers: sunfish, sunfishcode

Subscribers: sunfishcode, jfb, llvm-commits

Differential revision: http://reviews.llvm.org/D12219

git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@245887 91177308-0d34-0410-b5e6-96231b3b80d8

lib/Target/WebAssembly/InstPrinter/WebAssemblyInstPrinter.cpp
lib/Target/WebAssembly/InstPrinter/WebAssemblyInstPrinter.h
lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp
lib/Target/WebAssembly/WebAssemblyISD.def [new file with mode: 0644]
lib/Target/WebAssembly/WebAssemblyISelLowering.cpp
lib/Target/WebAssembly/WebAssemblyISelLowering.h
lib/Target/WebAssembly/WebAssemblyInstrCall.td
lib/Target/WebAssembly/WebAssemblyInstrControl.td
lib/Target/WebAssembly/WebAssemblyInstrFormats.td
lib/Target/WebAssembly/WebAssemblyInstrInfo.td
test/CodeGen/WebAssembly/call.ll

index 0c5792c6b51a656344cd0695f60cfd681905bd32..cbd2f7dc748a717dc7f02357352110f15db11587 100644 (file)
@@ -44,3 +44,16 @@ void WebAssemblyInstPrinter::printInst(const MCInst *MI, raw_ostream &OS,
   printInstruction(MI, OS);
   printAnnotation(OS, Annot);
 }
+
+void WebAssemblyInstPrinter::printOperand(const MCInst *MI, unsigned OpNo,
+                                          raw_ostream &O) {
+  const MCOperand &Op = MI->getOperand(OpNo);
+  if (Op.isReg())
+    O << getRegisterName(Op.getReg());
+  else if (Op.isImm())
+    O << '#' << Op.getImm();
+  else {
+    assert(Op.isExpr() && "unknown operand kind in printOperand");
+    Op.getExpr()->print(O, &MAI);
+  }
+}
index 4c54a525577de1c757a341f594e7837e521385aa..80fe29d56f71576c2cb850ec7452c287f8943af3 100644 (file)
@@ -32,6 +32,9 @@ public:
   void printInst(const MCInst *MI, raw_ostream &OS, StringRef Annot,
                  const MCSubtargetInfo &STI) override;
 
+  // Used by tblegen code.
+  void printOperand(const MCInst *MI, unsigned OpNo, raw_ostream &O);
+
   // Autogenerated by tblgen.
   void printInstruction(const MCInst *MI, raw_ostream &O);
   static const char *getRegisterName(unsigned RegNo);
index a3cbcbbeaf6dc33820a22cf22244050f259680eb..065ef87fb35a1e9f5638f4a6b2948a240c771e2a 100644 (file)
@@ -84,7 +84,10 @@ static SmallString<32> Name(const WebAssemblyInstrInfo *TII,
   return SmallString<32>(&N[0], &N[End]);
 }
 
+static std::string toSymbol(StringRef S) { return ("$" + S).str(); }
+
 void WebAssemblyAsmPrinter::EmitInstruction(const MachineInstr *MI) {
+  DEBUG(dbgs() << "EmitInstruction: " << *MI << '\n');
   SmallString<128> Str;
   raw_svector_ostream OS(Str);
 
@@ -132,6 +135,9 @@ void WebAssemblyAsmPrinter::EmitInstruction(const MachineInstr *MI) {
       assert(Written < BufBytes);
       OS << ' ' << buf;
     } break;
+    case MachineOperand::MO_GlobalAddress: {
+      OS << ' ' << toSymbol(MO.getGlobal()->getName());
+    } break;
     }
   OS << ')';
 
diff --git a/lib/Target/WebAssembly/WebAssemblyISD.def b/lib/Target/WebAssembly/WebAssemblyISD.def
new file mode 100644 (file)
index 0000000..0bc9b2f
--- /dev/null
@@ -0,0 +1,22 @@
+//- WebAssemblyISD.def - WebAssembly ISD ---------------------------*- C++ -*-//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// \brief This file describes the various WebAssembly ISD node types.
+///
+//===----------------------------------------------------------------------===//
+
+// NOTE: NO INCLUDE GUARD DESIRED!
+
+HANDLE_NODETYPE(CALL)
+HANDLE_NODETYPE(RETURN)
+HANDLE_NODETYPE(ARGUMENT)
+HANDLE_NODETYPE(Wrapper)
+
+// add memory opcodes starting at ISD::FIRST_TARGET_MEMORY_OPCODE here...
index 98a5b6829b6745b891cf402bdec006e89f2e5ad5..c309db48f6380425ba60528ad8d3a36f94cc8b5a 100644 (file)
@@ -19,6 +19,7 @@
 #include "WebAssemblyTargetMachine.h"
 #include "WebAssemblyTargetObjectFile.h"
 #include "llvm/CodeGen/Analysis.h"
+#include "llvm/CodeGen/CallingConvLower.h"
 #include "llvm/CodeGen/MachineRegisterInfo.h"
 #include "llvm/CodeGen/SelectionDAG.h"
 #include "llvm/IR/DiagnosticInfo.h"
@@ -92,6 +93,8 @@ int DiagnosticInfoUnsupported::KindID = 0;
 WebAssemblyTargetLowering::WebAssemblyTargetLowering(
     const TargetMachine &TM, const WebAssemblySubtarget &STI)
     : TargetLowering(TM), Subtarget(&STI) {
+  auto MVTPtr = Subtarget->hasAddr64() ? MVT::i64 : MVT::i32;
+
   // Booleans always contain 0 or 1.
   setBooleanContents(ZeroOrOneBooleanContent);
   // WebAssembly does not produce floating-point exceptions on normal floating
@@ -112,6 +115,8 @@ WebAssemblyTargetLowering::WebAssemblyTargetLowering(
 
   // FIXME: many setOperationAction are missing...
 
+  setOperationAction(ISD::GlobalAddress, MVTPtr, Custom);
+
   for (auto T : {MVT::f32, MVT::f64}) {
     // Don't expand the floating-point types to constant pools.
     setOperationAction(ISD::ConstantFP, T, Legal);
@@ -156,6 +161,13 @@ FastISel *WebAssemblyTargetLowering::createFastISel(
   return WebAssembly::createFastISel(FuncInfo, LibInfo);
 }
 
+bool WebAssemblyTargetLowering::isOffsetFoldingLegal(
+    const GlobalAddressSDNode *GA) const {
+  // The WebAssembly target doesn't support folding offsets into global
+  // addresses.
+  return false;
+}
+
 MVT WebAssemblyTargetLowering::getScalarShiftAmountTy(const DataLayout &DL,
                                                       EVT VT) const {
   return VT.getSimpleVT();
@@ -164,9 +176,13 @@ MVT WebAssemblyTargetLowering::getScalarShiftAmountTy(const DataLayout &DL,
 const char *
 WebAssemblyTargetLowering::getTargetNodeName(unsigned Opcode) const {
   switch (static_cast<WebAssemblyISD::NodeType>(Opcode)) {
-  case WebAssemblyISD::FIRST_NUMBER: break;
-  case WebAssemblyISD::RETURN: return "WebAssemblyISD::RETURN";
-  case WebAssemblyISD::ARGUMENT: return "WebAssemblyISD::ARGUMENT";
+  case WebAssemblyISD::FIRST_NUMBER:
+    break;
+#define HANDLE_NODETYPE(NODE)                                                  \
+  case WebAssemblyISD::NODE:                                                   \
+    return "WebAssemblyISD::" #NODE;
+#include "WebAssemblyISD.def"
+#undef HANDLE_NODETYPE
   }
   return nullptr;
 }
@@ -185,7 +201,6 @@ static void fail(SDLoc DL, SelectionDAG &DAG, const char *msg) {
       DiagnosticInfoUnsupported(DL, *MF.getFunction(), msg, SDValue()));
 }
 
-<<<<<<< HEAD
 SDValue
 WebAssemblyTargetLowering::LowerCall(CallLoweringInfo &CLI,
                                      SmallVectorImpl<SDValue> &InVals) const {
@@ -205,7 +220,6 @@ WebAssemblyTargetLowering::LowerCall(CallLoweringInfo &CLI,
 
   SmallVectorImpl<ISD::OutputArg> &Outs = CLI.Outs;
   SmallVectorImpl<SDValue> &OutVals = CLI.OutVals;
-  Type *retTy = CLI.RetTy;
   bool IsStructRet = (Outs.empty()) ? false : Outs[0].Flags.isSRet();
   if (IsStructRet)
     fail(DL, DAG, "WebAssembly doesn't support struct return yet");
@@ -213,7 +227,6 @@ WebAssemblyTargetLowering::LowerCall(CallLoweringInfo &CLI,
     fail(DL, DAG, "WebAssembly doesn't support more than 1 returned value yet");
 
   SmallVectorImpl<ISD::InputArg> &Ins = CLI.Ins;
-  ArgListTy &Args = CLI.getArgs();
   bool IsVarArg = CLI.IsVarArg;
   if (IsVarArg)
     fail(DL, DAG, "WebAssembly doesn't support varargs yet");
@@ -223,33 +236,35 @@ WebAssemblyTargetLowering::LowerCall(CallLoweringInfo &CLI,
   unsigned NumBytes = CCInfo.getNextStackOffset();
 
   auto PtrVT = getPointerTy(MF.getDataLayout());
-  auto Zero = DAG.getConstant(0, CLI.DL, PtrVT, true);
-  auto NB = DAG.getConstant(NumBytes, CLI.DL, PtrVT, true);
-  Chain = DAG.getCALLSEQ_START(Chain, NB, CLI.DL);
+  auto Zero = DAG.getConstant(0, DL, PtrVT, true);
+  auto NB = DAG.getConstant(NumBytes, DL, PtrVT, true);
+  Chain = DAG.getCALLSEQ_START(Chain, NB, DL);
 
   SmallVector<SDValue, 16> Ops;
   Ops.push_back(Chain);
-  Ops.push_back(CLI.Callee);
-  Ops.append(CLI.OutVals.begin(), CLI.OutVals.end());
+  Ops.push_back(Callee);
+  Ops.append(OutVals.begin(), OutVals.end());
 
   SmallVector<EVT, 8> Tys;
-  for (const auto &In : CLI.Ins)
+  for (const auto &In : Ins)
     Tys.push_back(In.VT);
   Tys.push_back(MVT::Other);
-  SDVTList TyList = CLI.DAG.getVTList(Tys);
-  SDValue Res = CLI.DAG.getNode(WebAssemblyISD::CALL, CLI.DL, TyList, Ops);
-  InVals.push_back(Res);
-  Chain = Res.getValue(1);
+  SDVTList TyList = DAG.getVTList(Tys);
+  SDValue Res = DAG.getNode(WebAssemblyISD::CALL, DL, TyList, Ops);
+  if (Ins.empty()) {
+    Chain = Res;
+  } else {
+    InVals.push_back(Res);
+    Chain = Res.getValue(1);
+  }
 
   // FIXME: handle CLI.RetSExt and CLI.RetZExt?
 
-  Chain = CLI.DAG.getCALLSEQ_END(Chain, NB, Zero, SDValue(), CLI.DL);
+  Chain = DAG.getCALLSEQ_END(Chain, NB, Zero, SDValue(), DL);
 
   return Chain;
 }
 
-=======
->>>>>>> parent of 03685a9... call
 bool WebAssemblyTargetLowering::CanLowerReturn(
     CallingConv::ID CallConv, MachineFunction &MF, bool IsVarArg,
     const SmallVectorImpl<ISD::OutputArg> &Outs, LLVMContext &Context) const {
@@ -326,9 +341,34 @@ SDValue WebAssemblyTargetLowering::LowerFormalArguments(
 }
 
 //===----------------------------------------------------------------------===//
-//  Other Lowering Code
+//  Custom lowering hooks.
 //===----------------------------------------------------------------------===//
 
+SDValue WebAssemblyTargetLowering::LowerOperation(SDValue Op,
+                                                  SelectionDAG &DAG) const {
+  switch (Op.getOpcode()) {
+  default:
+    llvm_unreachable("unimplemented operation lowering");
+    return SDValue();
+  case ISD::GlobalAddress:
+    return LowerGlobalAddress(Op, DAG);
+  }
+}
+
+SDValue WebAssemblyTargetLowering::LowerGlobalAddress(SDValue Op,
+                                                      SelectionDAG &DAG) const {
+  SDLoc DL(Op);
+  const auto *GA = cast<GlobalAddressSDNode>(Op);
+  EVT VT = Op.getValueType();
+  assert(GA->getOffset() == 0 &&
+         "offsets on global addresses are forbidden by isOffsetFoldingLegal");
+  assert(GA->getTargetFlags() == 0 && "WebAssembly doesn't set target flags");
+  if (GA->getAddressSpace() != 0)
+    fail(DL, DAG, "WebAssembly only expects the 0 address space");
+  return DAG.getNode(WebAssemblyISD::Wrapper, DL, VT,
+                     DAG.getTargetGlobalAddress(GA->getGlobal(), DL, VT));
+}
+
 //===----------------------------------------------------------------------===//
 //                          WebAssembly Optimization Hooks
 //===----------------------------------------------------------------------===//
index ea845cd4031c5e5158515d39f0db6549144874d2..119d0f5ef43cca4c2caea8185ad2237850e21d1b 100644 (file)
@@ -24,10 +24,9 @@ namespace WebAssemblyISD {
 
 enum NodeType : unsigned {
   FIRST_NUMBER = ISD::BUILTIN_OP_END,
-  RETURN,
-  ARGUMENT,
-
-  // add memory opcodes starting at ISD::FIRST_TARGET_MEMORY_OPCODE here...
+#define HANDLE_NODETYPE(NODE) NODE,
+#include "WebAssemblyISD.def"
+#undef HANDLE_NODETYPE
 };
 
 } // end namespace WebAssemblyISD
@@ -47,26 +46,29 @@ private:
 
   FastISel *createFastISel(FunctionLoweringInfo &FuncInfo,
                            const TargetLibraryInfo *LibInfo) const override;
-
+  bool isOffsetFoldingLegal(const GlobalAddressSDNode *GA) const override;
   MVT getScalarShiftAmountTy(const DataLayout &DL, EVT) const override;
-
   const char *getTargetNodeName(unsigned Opcode) const override;
 
+  SDValue LowerCall(CallLoweringInfo &CLI,
+                    SmallVectorImpl<SDValue> &InVals) const override;
   bool CanLowerReturn(CallingConv::ID CallConv, MachineFunction &MF,
                       bool isVarArg,
                       const SmallVectorImpl<ISD::OutputArg> &Outs,
                       LLVMContext &Context) const override;
-
   SDValue LowerReturn(SDValue Chain, CallingConv::ID CallConv, bool isVarArg,
                       const SmallVectorImpl<ISD::OutputArg> &Outs,
                       const SmallVectorImpl<SDValue> &OutVals, SDLoc dl,
                       SelectionDAG &DAG) const override;
-
   SDValue LowerFormalArguments(SDValue Chain, CallingConv::ID CallConv,
                                bool IsVarArg,
                                const SmallVectorImpl<ISD::InputArg> &Ins,
                                SDLoc DL, SelectionDAG &DAG,
                                SmallVectorImpl<SDValue> &InVals) const override;
+
+  // Custom lowering hooks.
+  SDValue LowerOperation(SDValue Op, SelectionDAG &DAG) const override;
+  SDValue LowerGlobalAddress(SDValue Op, SelectionDAG &DAG) const;
 };
 
 namespace WebAssembly {
index 6b5b6cd5417334623b1f62b91230ad4afd4f0a52..abb7f21512a7915d47e537404132c6d2f72fcbf4 100644 (file)
 ///
 //===----------------------------------------------------------------------===//
 
+// The call sequence start/end LLVM-isms isn't useful to WebAssembly since it's
+// a virtual ISA.
+let isCodeGenOnly = 1 in {
+def : I<(outs), (ins i64imm:$amt),
+        [(WebAssemblycallseq_start timm:$amt)]>;
+def : I<(outs), (ins i64imm:$amt1, i64imm:$amt2),
+        [(WebAssemblycallseq_end timm:$amt1, timm:$amt2)]>;
+} // isCodeGenOnly = 1
+
+multiclass CALL<WebAssemblyRegClass vt> {
+  def CALL_#vt : I<(outs vt:$dst), (ins Int32:$callee, variable_ops),
+                   [(set vt:$dst, (WebAssemblycall Int32:$callee))]>;
+}
+let Uses = [SP32, SP64], isCall = 1 in {
+  defm : CALL<Int32>;
+  defm : CALL<Int64>;
+  defm : CALL<Float32>;
+  defm : CALL<Float64>;
+  // FIXME: void.
+} // Uses = [SP32,SP64], isCall = 1
+
 /*
  * TODO(jfb): Add the following.
  *
index 3025e24fb843058dac36bba14878537db641eb99..5f53e4a00d45cdfc267d68601552e7076a87f594 100644 (file)
 multiclass RETURN<WebAssemblyRegClass vt> {
   def RETURN_#vt : I<(outs), (ins vt:$val), [(WebAssemblyreturn vt:$val)]>;
 }
-let hasSideEffects = 1, isReturn = 1, isTerminator = 1, hasCtrlDep = 1,
-    isBarrier = 1 in {
+let isReturn = 1, isTerminator = 1, hasCtrlDep = 1, isBarrier = 1 in {
   defm : RETURN<Int32>;
   defm : RETURN<Int64>;
   defm : RETURN<Float32>;
   defm : RETURN<Float64>;
   def RETURN_VOID : I<(outs), (ins), [(WebAssemblyreturn)]>;
-} // hasSideEffects = 1, isReturn = 1, isTerminator = 1, hasCtrlDep = 1,
-  // isBarrier = 1
+} // isReturn = 1, isTerminator = 1, hasCtrlDep = 1, isBarrier = 1
index f4d16d39e646172f5763ea0df05751600b8abcad..61de5ddd716b1c8ff046a05b0fd4f6672f083682 100644 (file)
@@ -12,7 +12,7 @@
 ///
 //===----------------------------------------------------------------------===//
 
-// WebAssembly Instruction Format
+// WebAssembly Instruction Format.
 class WebAssemblyInst<string cstr> : Instruction {
   field bits<0> Inst; // Instruction encoding.
   let Namespace   = "WebAssembly";
@@ -20,7 +20,7 @@ class WebAssemblyInst<string cstr> : Instruction {
   let Constraints = cstr;
 }
 
-// Normal instructions
+// Normal instructions.
 class I<dag oops, dag iops, list<dag> pattern, string cstr = "">
     : WebAssemblyInst<cstr> {
   dag OutOperandList = oops;
index c6335723b4c2f39e496fb2679365ba8b3f246e98..d73e66ea985d1c5d3e86cbc83836b8e796a7d0d0 100644 (file)
@@ -25,18 +25,34 @@ def HasSIMD128 : Predicate<"Subtarget->hasSIMD128()">,
 // WebAssembly-specific DAG Node Types.
 //===----------------------------------------------------------------------===//
 
+def SDT_WebAssemblyCallSeqStart : SDCallSeqStart<[SDTCisVT<0, iPTR>]>;
+def SDT_WebAssemblyCallSeqEnd :
+    SDCallSeqEnd<[SDTCisVT<0, iPTR>, SDTCisVT<1, iPTR>]>;
+def SDT_WebAssemblyCall     : SDTypeProfile<1, -1, [SDTCisPtrTy<1>]>;
 def SDT_WebAssemblyArgument : SDTypeProfile<1, 1, [SDTCisVT<1, i32>]>;
 def SDT_WebAssemblyReturn   : SDTypeProfile<0, -1, []>;
+def SDT_WebAssemblyWrapper  : SDTypeProfile<1, 1, [SDTCisSameAs<0, 1>,
+                                                   SDTCisPtrTy<0>]>;
 
 //===----------------------------------------------------------------------===//
 // WebAssembly-specific DAG Nodes.
 //===----------------------------------------------------------------------===//
 
+def WebAssemblycallseq_start :
+    SDNode<"ISD::CALLSEQ_START", SDT_WebAssemblyCallSeqStart,
+           [SDNPHasChain, SDNPOutGlue]>;
+def WebAssemblycallseq_end :
+    SDNode<"ISD::CALLSEQ_END", SDT_WebAssemblyCallSeqEnd,
+           [SDNPHasChain, SDNPOptInGlue, SDNPOutGlue]>;
+def WebAssemblycall      : SDNode<"WebAssemblyISD::CALL",
+                                  SDT_WebAssemblyCall,
+                                  [SDNPHasChain, SDNPVariadic]>;
 def WebAssemblyargument : SDNode<"WebAssemblyISD::ARGUMENT",
                                  SDT_WebAssemblyArgument>;
 def WebAssemblyreturn   : SDNode<"WebAssemblyISD::RETURN",
-                                 SDT_WebAssemblyReturn,
-                                 [SDNPHasChain, SDNPSideEffect]>;
+                                 SDT_WebAssemblyReturn, [SDNPHasChain]>;
+def WebAssemblywrapper  : SDNode<"WebAssemblyISD::Wrapper",
+                                 SDT_WebAssemblyWrapper>;
 
 //===----------------------------------------------------------------------===//
 // WebAssembly-specific Operands.
@@ -49,6 +65,8 @@ def WebAssemblyreturn   : SDNode<"WebAssemblyISD::RETURN",
  * set_local: set the current value of a local variable
 */
 
+def global : Operand<iPTR>;
+
 //===----------------------------------------------------------------------===//
 // WebAssembly Instruction Format Definitions.
 //===----------------------------------------------------------------------===//
@@ -74,6 +92,10 @@ def Immediate_F32 : I<(outs Float32:$res), (ins f32imm:$imm),
 def Immediate_F64 : I<(outs Float64:$res), (ins f64imm:$imm),
                       [(set Float64:$res, fpimm:$imm)]>;
 
+// Special types of immediates.
+def GLOBAL : I<(outs Int32:$dst), (ins global:$addr),
+               [(set Int32:$dst, (WebAssemblywrapper tglobaladdr:$addr))]>;
+
 //===----------------------------------------------------------------------===//
 // Additional sets of instructions.
 //===----------------------------------------------------------------------===//
index e5d468b3cd1c2a2bffcfac9e9bbc84e00fb82f30..ae66fae840c10b7b80923018c97a5dc0865f2720 100644 (file)
@@ -5,18 +5,61 @@
 target datalayout = "e-p:32:32-i64:64-v128:8:128-n32:64-S128"
 target triple = "wasm32-unknown-unknown"
 
-declare void @nullary()
-
-; CHECK-LABEL: call_nullary:
-; CHECK-NEXT: (call @foo)
-; CHECK-NEXT: (return)
-define void @call_nullary() {
-  call void @nullary()
-  ret void
+declare i32 @i32_nullary()
+declare i32 @i32_unary(i32)
+declare i64 @i64_nullary()
+declare float @float_nullary()
+declare double @double_nullary()
+
+; CHECK-LABEL: call_i32_nullary:
+; CHECK-NEXT: (setlocal @0 (global $i32_nullary))
+; CHECK-NEXT: (setlocal @1 (call @0))
+; CHECK-NEXT: (return @1)
+define i32 @call_i32_nullary() {
+  %r = call i32 @i32_nullary()
+  ret i32 %r
+}
+
+; CHECK-LABEL: call_i64_nullary:
+; CHECK-NEXT: (setlocal @0 (global $i64_nullary))
+; CHECK-NEXT: (setlocal @1 (call @0))
+; CHECK-NEXT: (return @1)
+define i64 @call_i64_nullary() {
+  %r = call i64 @i64_nullary()
+  ret i64 %r
+}
+
+; CHECK-LABEL: call_float_nullary:
+; CHECK-NEXT: (setlocal @0 (global $float_nullary))
+; CHECK-NEXT: (setlocal @1 (call @0))
+; CHECK-NEXT: (return @1)
+define float @call_float_nullary() {
+  %r = call float @float_nullary()
+  ret float %r
+}
+
+; CHECK-LABEL: call_double_nullary:
+; CHECK-NEXT: (setlocal @0 (global $double_nullary))
+; CHECK-NEXT: (setlocal @1 (call @0))
+; CHECK-NEXT: (return @1)
+define double @call_double_nullary() {
+  %r = call double @double_nullary()
+  ret double %r
 }
 
+; CHECK-LABEL: call_i32_unary:
+; CHECK-NEXT: (setlocal @0 (argument 0))
+; CHECK-NEXT: (setlocal @1 (global $i32_unary))
+; CHECK-NEXT: (setlocal @2 (call @1 @0))
+; CHECK-NEXT: (return @2)
+define i32 @call_i32_unary(i32 %a) {
+  %r = call i32 @i32_unary(i32 %a)
+  ret i32 %r
+}
 
-; tail call
-; multiple args
-; interesting returns (int, float, struct, multiple)
-; vararg
+; FIXME test the following:
+;  - Functions without return.
+;  - More argument combinations.
+;  - Tail call.
+;  - Interesting returns (struct, multiple).
+;  - Vararg.