Skip to content

Commit

Permalink
[WebAssembly] Support for WebAssembly globals in LLVM IR
Browse files Browse the repository at this point in the history
This patch adds support for WebAssembly globals in LLVM IR, representing
them as pointers to global values, in a non-default, non-integral
address space.  Instruction selection legalizes loads and stores to
these pointers to new WebAssemblyISD nodes GLOBAL_GET and GLOBAL_SET.
Once the lowering creates the new nodes, tablegen pattern matches those
and converts them to Wasm global.get/set of the appropriate type.

Based on work by Paulo Matos in https://reviews.llvm.org/D95425.

Reviewed By: pmatos

Differential Revision: https://reviews.llvm.org/D101608
  • Loading branch information
pmatos authored and wingo committed May 11, 2021
1 parent 04adfb6 commit d7086af
Show file tree
Hide file tree
Showing 12 changed files with 271 additions and 47 deletions.
4 changes: 2 additions & 2 deletions clang/lib/Basic/Targets/WebAssembly.h
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ class LLVM_LIBRARY_VISIBILITY WebAssembly32TargetInfo
explicit WebAssembly32TargetInfo(const llvm::Triple &T,
const TargetOptions &Opts)
: WebAssemblyTargetInfo(T, Opts) {
resetDataLayout("e-m:e-p:32:32-i64:64-n32:64-S128");
resetDataLayout("e-m:e-p:32:32-i64:64-n32:64-S128-ni:1");
}

protected:
Expand All @@ -166,7 +166,7 @@ class LLVM_LIBRARY_VISIBILITY WebAssembly64TargetInfo
SizeType = UnsignedLong;
PtrDiffType = SignedLong;
IntPtrType = SignedLong;
resetDataLayout("e-m:e-p:64:64-i64:64-n32:64-S128");
resetDataLayout("e-m:e-p:64:64-i64:64-n32:64-S128-ni:1");
}

protected:
Expand Down
4 changes: 2 additions & 2 deletions clang/test/CodeGen/target-data.c
Original file line number Diff line number Diff line change
Expand Up @@ -108,11 +108,11 @@

// RUN: %clang_cc1 -triple wasm32-unknown-unknown -o - -emit-llvm %s | \
// RUN: FileCheck %s -check-prefix=WEBASSEMBLY32
// WEBASSEMBLY32: target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
// WEBASSEMBLY32: target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128-ni:1"

// RUN: %clang_cc1 -triple wasm64-unknown-unknown -o - -emit-llvm %s | \
// RUN: FileCheck %s -check-prefix=WEBASSEMBLY64
// WEBASSEMBLY64: target datalayout = "e-m:e-p:64:64-i64:64-n32:64-S128"
// WEBASSEMBLY64: target datalayout = "e-m:e-p:64:64-i64:64-n32:64-S128-ni:1"

// RUN: %clang_cc1 -triple lanai-unknown-unknown -o - -emit-llvm %s | \
// RUN: FileCheck %s -check-prefix=LANAI
Expand Down
20 changes: 20 additions & 0 deletions llvm/lib/Target/WebAssembly/Utils/WebAssemblyUtilities.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,26 @@ class WebAssemblySubtarget;

namespace WebAssembly {

enum WasmAddressSpace : unsigned {
// Default address space, for pointers to linear memory (stack, heap, data).
WASM_ADDRESS_SPACE_DEFAULT = 0,
// A non-integral address space for pointers to named objects outside of
// linear memory: WebAssembly globals or WebAssembly locals. Loads and stores
// to these pointers are lowered to global.get / global.set or local.get /
// local.set, as appropriate.
WASM_ADDRESS_SPACE_WASM_VAR = 1
};

inline bool isDefaultAddressSpace(unsigned AS) {
return AS == WASM_ADDRESS_SPACE_DEFAULT;
}
inline bool isWasmVarAddressSpace(unsigned AS) {
return AS == WASM_ADDRESS_SPACE_WASM_VAR;
}
inline bool isValidAddressSpace(unsigned AS) {
return isDefaultAddressSpace(AS) || isWasmVarAddressSpace(AS);
}

bool isChild(const MachineInstr &MI, const WebAssemblyFunctionInfo &MFI);
bool mayThrow(const MachineInstr &MI);

Expand Down
4 changes: 4 additions & 0 deletions llvm/lib/Target/WebAssembly/WebAssemblyFastISel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1182,6 +1182,8 @@ bool WebAssemblyFastISel::selectLoad(const Instruction *I) {
const auto *Load = cast<LoadInst>(I);
if (Load->isAtomic())
return false;
if (!WebAssembly::isDefaultAddressSpace(Load->getPointerAddressSpace()))
return false;
if (!Subtarget->hasSIMD128() && Load->getType()->isVectorTy())
return false;

Expand Down Expand Up @@ -1240,6 +1242,8 @@ bool WebAssemblyFastISel::selectStore(const Instruction *I) {
const auto *Store = cast<StoreInst>(I);
if (Store->isAtomic())
return false;
if (!WebAssembly::isDefaultAddressSpace(Store->getPointerAddressSpace()))
return false;
if (!Subtarget->hasSIMD128() &&
Store->getValueOperand()->getType()->isVectorTy())
return false;
Expand Down
4 changes: 3 additions & 1 deletion llvm/lib/Target/WebAssembly/WebAssemblyISD.def
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ HANDLE_NODETYPE(RETURN)
HANDLE_NODETYPE(ARGUMENT)
// A wrapper node for TargetExternalSymbol, TargetGlobalAddress, and MCSymbol
HANDLE_NODETYPE(Wrapper)
// A special wapper used in PIC code for __memory_base/__table_base relcative
// A special wapper used in PIC code for __memory_base/__table_base relative
// access.
HANDLE_NODETYPE(WrapperPIC)
HANDLE_NODETYPE(BR_IF)
Expand All @@ -44,3 +44,5 @@ HANDLE_NODETYPE(MEMORY_FILL)

// Memory intrinsics
HANDLE_MEM_NODETYPE(LOAD_SPLAT)
HANDLE_MEM_NODETYPE(GLOBAL_GET)
HANDLE_MEM_NODETYPE(GLOBAL_SET)
74 changes: 71 additions & 3 deletions llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@
#include "WebAssemblyMachineFunctionInfo.h"
#include "WebAssemblySubtarget.h"
#include "WebAssemblyTargetMachine.h"
#include "llvm/CodeGen/Analysis.h"
#include "llvm/CodeGen/CallingConvLower.h"
#include "llvm/CodeGen/MachineInstrBuilder.h"
#include "llvm/CodeGen/MachineJumpTableInfo.h"
#include "llvm/CodeGen/MachineModuleInfo.h"
#include "llvm/CodeGen/MachineRegisterInfo.h"
#include "llvm/CodeGen/SelectionDAG.h"
#include "llvm/CodeGen/SelectionDAGNodes.h"
#include "llvm/CodeGen/WasmEHFuncInfo.h"
#include "llvm/IR/DiagnosticInfo.h"
#include "llvm/IR/DiagnosticPrinter.h"
Expand Down Expand Up @@ -69,6 +69,20 @@ WebAssemblyTargetLowering::WebAssemblyTargetLowering(
// Compute derived properties from the register classes.
computeRegisterProperties(Subtarget->getRegisterInfo());

// Transform loads and stores to pointers in address space 1 to loads and
// stores to WebAssembly global variables, outside linear memory.
for (auto T : {MVT::i32, MVT::i64, MVT::f32, MVT::f64}) {
setOperationAction(ISD::LOAD, T, Custom);
setOperationAction(ISD::STORE, T, Custom);
}
if (Subtarget->hasSIMD128()) {
for (auto T : {MVT::v16i8, MVT::v8i16, MVT::v4i32, MVT::v4f32, MVT::v2i64,
MVT::v2f64}) {
setOperationAction(ISD::LOAD, T, Custom);
setOperationAction(ISD::STORE, T, Custom);
}
}

setOperationAction(ISD::GlobalAddress, MVTPtr, Custom);
setOperationAction(ISD::GlobalTLSAddress, MVTPtr, Custom);
setOperationAction(ISD::ExternalSymbol, MVTPtr, Custom);
Expand Down Expand Up @@ -1248,9 +1262,63 @@ SDValue WebAssemblyTargetLowering::LowerOperation(SDValue Op,
case ISD::FP_TO_SINT_SAT:
case ISD::FP_TO_UINT_SAT:
return LowerFP_TO_INT_SAT(Op, DAG);
case ISD::LOAD:
return LowerLoad(Op, DAG);
case ISD::STORE:
return LowerStore(Op, DAG);
}
}

static bool IsWebAssemblyGlobal(SDValue Op) {
if (const GlobalAddressSDNode *GA = dyn_cast<GlobalAddressSDNode>(Op))
return WebAssembly::isWasmVarAddressSpace(GA->getAddressSpace());

return false;
}

SDValue WebAssemblyTargetLowering::LowerStore(SDValue Op,
SelectionDAG &DAG) const {
SDLoc DL(Op);
StoreSDNode *SN = cast<StoreSDNode>(Op.getNode());
const SDValue &Value = SN->getValue();
const SDValue &Base = SN->getBasePtr();
const SDValue &Offset = SN->getOffset();

if (IsWebAssemblyGlobal(Base)) {
if (!Offset->isUndef())
report_fatal_error("unexpected offset when storing to webassembly global",
false);

SDVTList Tys = DAG.getVTList(MVT::Other);
SDValue Ops[] = {SN->getChain(), Value, Base};
return DAG.getMemIntrinsicNode(WebAssemblyISD::GLOBAL_SET, DL, Tys, Ops,
SN->getMemoryVT(), SN->getMemOperand());
}

return Op;
}

SDValue WebAssemblyTargetLowering::LowerLoad(SDValue Op,
SelectionDAG &DAG) const {
SDLoc DL(Op);
LoadSDNode *LN = cast<LoadSDNode>(Op.getNode());
const SDValue &Base = LN->getBasePtr();
const SDValue &Offset = LN->getOffset();

if (IsWebAssemblyGlobal(Base)) {
if (!Offset->isUndef())
report_fatal_error(
"unexpected offset when loading from webassembly global", false);

SDVTList Tys = DAG.getVTList(LN->getValueType(0), MVT::Other);
SDValue Ops[] = {LN->getChain(), Base};
return DAG.getMemIntrinsicNode(WebAssemblyISD::GLOBAL_GET, DL, Tys, Ops,
LN->getMemoryVT(), LN->getMemOperand());
}

return Op;
}

SDValue WebAssemblyTargetLowering::LowerCopyToReg(SDValue Op,
SelectionDAG &DAG) const {
SDValue Src = Op.getOperand(2);
Expand Down Expand Up @@ -1369,8 +1437,8 @@ SDValue WebAssemblyTargetLowering::LowerGlobalAddress(SDValue Op,
EVT VT = Op.getValueType();
assert(GA->getTargetFlags() == 0 &&
"Unexpected target flags on generic GlobalAddressSDNode");
if (GA->getAddressSpace() != 0)
fail(DL, DAG, "WebAssembly only expects the 0 address space");
if (!WebAssembly::isValidAddressSpace(GA->getAddressSpace()))
fail(DL, DAG, "Invalid address space for WebAssembly target");

unsigned OperandFlags = 0;
if (isPositionIndependent()) {
Expand Down
2 changes: 2 additions & 0 deletions llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.h
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ class WebAssemblyTargetLowering final : public TargetLowering {
SDValue LowerAccessVectorElement(SDValue Op, SelectionDAG &DAG) const;
SDValue LowerShift(SDValue Op, SelectionDAG &DAG) const;
SDValue LowerFP_TO_INT_SAT(SDValue Op, SelectionDAG &DAG) const;
SDValue LowerLoad(SDValue Op, SelectionDAG &DAG) const;
SDValue LowerStore(SDValue Op, SelectionDAG &DAG) const;

// Custom DAG combine hooks
SDValue
Expand Down
50 changes: 33 additions & 17 deletions llvm/lib/Target/WebAssembly/WebAssemblyInstrInfo.td
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ def SDT_WebAssemblyWrapperPIC : SDTypeProfile<1, 1, [SDTCisSameAs<0, 1>,
SDTCisPtrTy<0>]>;
def SDT_WebAssemblyThrow : SDTypeProfile<0, -1, []>;
def SDT_WebAssemblyCatch : SDTypeProfile<1, 1, [SDTCisPtrTy<0>]>;
def SDT_WebAssemblyGlobalGet : SDTypeProfile<1, 1, [SDTCisPtrTy<1>]>;
def SDT_WebAssemblyGlobalSet : SDTypeProfile<0, 2, [SDTCisPtrTy<1>]>;

//===----------------------------------------------------------------------===//
// WebAssembly-specific DAG Nodes.
Expand Down Expand Up @@ -106,6 +108,12 @@ def WebAssemblythrow : SDNode<"WebAssemblyISD::THROW", SDT_WebAssemblyThrow,
[SDNPHasChain, SDNPVariadic]>;
def WebAssemblycatch : SDNode<"WebAssemblyISD::CATCH", SDT_WebAssemblyCatch,
[SDNPHasChain, SDNPSideEffect]>;
def WebAssemblyglobal_get :
SDNode<"WebAssemblyISD::GLOBAL_GET", SDT_WebAssemblyGlobalGet,
[SDNPHasChain, SDNPMayLoad, SDNPMemOperand]>;
def WebAssemblyglobal_set :
SDNode<"WebAssemblyISD::GLOBAL_SET", SDT_WebAssemblyGlobalSet,
[SDNPHasChain, SDNPMayStore, SDNPMemOperand]>;

//===----------------------------------------------------------------------===//
// WebAssembly-specific Operands.
Expand Down Expand Up @@ -241,12 +249,12 @@ include "WebAssemblyInstrFormats.td"
// Additional instructions.
//===----------------------------------------------------------------------===//

multiclass ARGUMENT<WebAssemblyRegClass reg, ValueType vt> {
multiclass ARGUMENT<WebAssemblyRegClass rc, ValueType vt> {
let hasSideEffects = 1, isCodeGenOnly = 1, Defs = []<Register>,
Uses = [ARGUMENTS] in
defm ARGUMENT_#vt :
I<(outs reg:$res), (ins i32imm:$argno), (outs), (ins i32imm:$argno),
[(set (vt reg:$res), (WebAssemblyargument timm:$argno))]>;
I<(outs rc:$res), (ins i32imm:$argno), (outs), (ins i32imm:$argno),
[(set (vt rc:$res), (WebAssemblyargument timm:$argno))]>;
}
defm "": ARGUMENT<I32, i32>;
defm "": ARGUMENT<I64, i64>;
Expand All @@ -257,66 +265,74 @@ defm "": ARGUMENT<EXTERNREF, externref>;

// local.get and local.set are not generated by instruction selection; they
// are implied by virtual register uses and defs.
multiclass LOCAL<WebAssemblyRegClass vt, Operand global_op> {
multiclass LOCAL<WebAssemblyRegClass rc, Operand global_op> {
let hasSideEffects = 0 in {
// COPY is not an actual instruction in wasm, but since we allow local.get and
// local.set to be implicit during most of codegen, we can have a COPY which
// is actually a no-op because all the work is done in the implied local.get
// and local.set. COPYs are eliminated (and replaced with
// local.get/local.set) in the ExplicitLocals pass.
let isAsCheapAsAMove = 1, isCodeGenOnly = 1 in
defm COPY_#vt : I<(outs vt:$res), (ins vt:$src), (outs), (ins), [],
defm COPY_#rc : I<(outs rc:$res), (ins rc:$src), (outs), (ins), [],
"local.copy\t$res, $src", "local.copy">;

// TEE is similar to COPY, but writes two copies of its result. Typically
// this would be used to stackify one result and write the other result to a
// local.
let isAsCheapAsAMove = 1, isCodeGenOnly = 1 in
defm TEE_#vt : I<(outs vt:$res, vt:$also), (ins vt:$src), (outs), (ins), [],
defm TEE_#rc : I<(outs rc:$res, rc:$also), (ins rc:$src), (outs), (ins), [],
"local.tee\t$res, $also, $src", "local.tee">;

// This is the actual local.get instruction in wasm. These are made explicit
// by the ExplicitLocals pass. It has mayLoad because it reads from a wasm
// local, which is a side effect not otherwise modeled in LLVM.
let mayLoad = 1, isAsCheapAsAMove = 1 in
defm LOCAL_GET_#vt : I<(outs vt:$res), (ins local_op:$local),
defm LOCAL_GET_#rc : I<(outs rc:$res), (ins local_op:$local),
(outs), (ins local_op:$local), [],
"local.get\t$res, $local", "local.get\t$local", 0x20>;

// This is the actual local.set instruction in wasm. These are made explicit
// by the ExplicitLocals pass. It has mayStore because it writes to a wasm
// local, which is a side effect not otherwise modeled in LLVM.
let mayStore = 1, isAsCheapAsAMove = 1 in
defm LOCAL_SET_#vt : I<(outs), (ins local_op:$local, vt:$src),
defm LOCAL_SET_#rc : I<(outs), (ins local_op:$local, rc:$src),
(outs), (ins local_op:$local), [],
"local.set\t$local, $src", "local.set\t$local", 0x21>;

// This is the actual local.tee instruction in wasm. TEEs are turned into
// LOCAL_TEEs by the ExplicitLocals pass. It has mayStore for the same reason
// as LOCAL_SET.
let mayStore = 1, isAsCheapAsAMove = 1 in
defm LOCAL_TEE_#vt : I<(outs vt:$res), (ins local_op:$local, vt:$src),
defm LOCAL_TEE_#rc : I<(outs rc:$res), (ins local_op:$local, rc:$src),
(outs), (ins local_op:$local), [],
"local.tee\t$res, $local, $src", "local.tee\t$local",
0x22>;

// Unused values must be dropped in some contexts.
defm DROP_#vt : I<(outs), (ins vt:$src), (outs), (ins), [],
defm DROP_#rc : I<(outs), (ins rc:$src), (outs), (ins), [],
"drop\t$src", "drop", 0x1a>;

let mayLoad = 1 in
defm GLOBAL_GET_#vt : I<(outs vt:$res), (ins global_op:$local),
(outs), (ins global_op:$local), [],
"global.get\t$res, $local", "global.get\t$local",
defm GLOBAL_GET_#rc : I<(outs rc:$res), (ins global_op:$addr),
(outs), (ins global_op:$addr), [],
"global.get\t$res, $addr", "global.get\t$addr",
0x23>;

let mayStore = 1 in
defm GLOBAL_SET_#vt : I<(outs), (ins global_op:$local, vt:$src),
(outs), (ins global_op:$local), [],
"global.set\t$local, $src", "global.set\t$local",
defm GLOBAL_SET_#rc : I<(outs), (ins global_op:$addr, rc:$src),
(outs), (ins global_op:$addr), [],
"global.set\t$addr, $src", "global.set\t$addr",
0x24>;

} // hasSideEffects = 0
} // hasSideEffects = 0
foreach vt = rc.RegTypes in {
def : Pat<(vt (WebAssemblyglobal_get
(WebAssemblywrapper tglobaladdr:$addr))),
(!cast<NI>("GLOBAL_GET_" # rc) tglobaladdr:$addr)>;
def : Pat<(WebAssemblyglobal_set
vt:$src, (WebAssemblywrapper tglobaladdr:$addr)),
(!cast<NI>("GLOBAL_SET_" # rc) tglobaladdr:$addr, vt:$src)>;
}
}
defm "" : LOCAL<I32, global_op32>;
defm "" : LOCAL<I64, global_op64>; // 64-bit only needed for pointers.
Expand Down
40 changes: 20 additions & 20 deletions llvm/lib/Target/WebAssembly/WebAssemblyInstrRef.td
Original file line number Diff line number Diff line change
Expand Up @@ -11,29 +11,29 @@
///
//===----------------------------------------------------------------------===//

multiclass REF_I<WebAssemblyRegClass reg, ValueType vt> {
defm REF_NULL_#reg : I<(outs reg:$res), (ins HeapType:$heaptype),
(outs), (ins HeapType:$heaptype),
[],
"ref.null\t$res, $heaptype",
"ref.null\t$heaptype",
0xd0>,
Requires<[HasReferenceTypes]>;
defm SELECT_#reg: I<(outs reg:$dst), (ins reg:$lhs, reg:$rhs, I32:$cond),
(outs), (ins),
[(set reg:$dst,
(select I32:$cond, reg:$lhs, reg:$rhs))],
vt#".select\t$dst, $lhs, $rhs, $cond",
vt#".select", 0x1b>,
Requires<[HasReferenceTypes]>;
multiclass REF_I<WebAssemblyRegClass rc, ValueType vt> {
defm REF_NULL_#rc : I<(outs rc:$res), (ins HeapType:$heaptype),
(outs), (ins HeapType:$heaptype),
[],
"ref.null\t$res, $heaptype",
"ref.null\t$heaptype",
0xd0>,
Requires<[HasReferenceTypes]>;
defm SELECT_#rc: I<(outs rc:$dst), (ins rc:$lhs, rc:$rhs, I32:$cond),
(outs), (ins),
[(set rc:$dst,
(select I32:$cond, rc:$lhs, rc:$rhs))],
vt#".select\t$dst, $lhs, $rhs, $cond",
vt#".select", 0x1b>,
Requires<[HasReferenceTypes]>;
}

defm "" : REF_I<FUNCREF, funcref>;
defm "" : REF_I<EXTERNREF, externref>;

foreach reg = [FUNCREF, EXTERNREF] in {
def : Pat<(select (i32 (setne I32:$cond, 0)), reg:$lhs, reg:$rhs),
(!cast<Instruction>("SELECT_"#reg) reg:$lhs, reg:$rhs, I32:$cond)>;
def : Pat<(select (i32 (seteq I32:$cond, 0)), reg:$lhs, reg:$rhs),
(!cast<Instruction>("SELECT_"#reg) reg:$rhs, reg:$lhs, I32:$cond)>;
foreach rc = [FUNCREF, EXTERNREF] in {
def : Pat<(select (i32 (setne I32:$cond, 0)), rc:$lhs, rc:$rhs),
(!cast<Instruction>("SELECT_"#rc) rc:$lhs, rc:$rhs, I32:$cond)>;
def : Pat<(select (i32 (seteq I32:$cond, 0)), rc:$lhs, rc:$rhs),
(!cast<Instruction>("SELECT_"#rc) rc:$rhs, rc:$lhs, I32:$cond)>;
}
Loading

0 comments on commit d7086af

Please sign in to comment.