Index: lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp =================================================================== --- lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp +++ lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp @@ -40,6 +40,7 @@ namespace { class WebAssemblyAsmPrinter final : public AsmPrinter { + bool hasAddr64; const WebAssemblyInstrInfo *TII; public: @@ -60,7 +61,9 @@ } bool runOnMachineFunction(MachineFunction &MF) override { - TII = MF.getSubtarget().getInstrInfo(); + const auto &Subtarget = MF.getSubtarget(); + hasAddr64 = Subtarget.hasAddr64(); + TII = Subtarget.getInstrInfo(); return AsmPrinter::runOnMachineFunction(MF); } @@ -97,23 +100,6 @@ static std::string toSymbol(StringRef S) { return ("$" + S).str(); } -static const char *toString(const Type *Ty) { - switch (Ty->getTypeID()) { - default: break; - case Type::FloatTyID: return "f32"; - case Type::DoubleTyID: return "f64"; - case Type::IntegerTyID: - switch (Ty->getIntegerBitWidth()) { - case 32: return "i32"; - case 64: return "i64"; - default: break; - } - } - DEBUG(dbgs() << "Invalid type "; Ty->print(dbgs()); dbgs() << '\n'); - llvm_unreachable("invalid type"); - return ""; -} - static std::string toString(const APFloat &FP) { static const size_t BufBytes = 128; char buf[BufBytes]; @@ -131,6 +117,28 @@ return buf; } +static const char *toString(const Type *Ty, bool hasAddr64) { + switch (Ty->getTypeID()) { + default: break; + // Treat all pointers as the underlying integer into linear memory. + case Type::PointerTyID: return hasAddr64 ? "i64" : "i32"; + case Type::FloatTyID: return "f32"; + case Type::DoubleTyID: return "f64"; + case Type::IntegerTyID: + switch (Ty->getIntegerBitWidth()) { + case 8: return "i8"; + case 16: return "i16"; + case 32: return "i32"; + case 64: return "i64"; + default: break; + } + } + DEBUG(dbgs() << "Invalid type "; Ty->print(dbgs()); dbgs() << '\n'); + llvm_unreachable("invalid type"); + return ""; +} + + //===----------------------------------------------------------------------===// // WebAssemblyAsmPrinter Implementation. //===----------------------------------------------------------------------===// @@ -186,7 +194,8 @@ return; } - OS << "(global " << toSymbol(Name) << ' ' << toString(Init->getType()) << ' '; + OS << "(global " << toSymbol(Name) << ' ' + << toString(Init->getType(), hasAddr64) << ' '; if (const auto *C = dyn_cast(Init)) { assert(C->getBitWidth() <= 64 && "Printing wider types unimplemented"); OS << C->getZExtValue(); @@ -228,10 +237,10 @@ raw_svector_ostream OS(Str); const Function *F = MF->getFunction(); for (const Argument &A : F->args()) - OS << " (param " << toString(A.getType()) << ')'; + OS << " (param " << toString(A.getType(), hasAddr64) << ')'; const Type *Rt = F->getReturnType(); if (!Rt->isVoidTy()) - OS << " (result " << toString(Rt) << ')'; + OS << " (result " << toString(Rt, hasAddr64) << ')'; OS << '\n'; OutStreamer->EmitRawText(OS.str()); } Index: lib/Target/WebAssembly/WebAssemblyISelLowering.cpp =================================================================== --- lib/Target/WebAssembly/WebAssemblyISelLowering.cpp +++ lib/Target/WebAssembly/WebAssemblyISelLowering.cpp @@ -113,8 +113,6 @@ // Compute derived properties from the register classes. computeRegisterProperties(Subtarget->getRegisterInfo()); - // FIXME: many setOperationAction are missing... - setOperationAction(ISD::GlobalAddress, MVTPtr, Custom); for (auto T : {MVT::f32, MVT::f64}) { @@ -154,6 +152,16 @@ setOperationAction(ISD::STACKSAVE, MVT::Other, Expand); setOperationAction(ISD::STACKRESTORE, MVT::Other, Expand); setOperationAction(ISD::DYNAMIC_STACKALLOC, MVTPtr, Expand); + + // WebAssembly doesn't have: + // - Floating-point extending loads. + // - Floating-point truncating stores. + // - i1 extending loads. + setLoadExtAction(ISD::EXTLOAD, MVT::f32, MVT::f64, Expand); + setTruncStoreAction(MVT::f64, MVT::f32, Expand); + for (auto T : MVT::integer_valuetypes()) + for (auto Ext : {ISD::EXTLOAD, ISD::ZEXTLOAD, ISD::SEXTLOAD}) + setLoadExtAction(Ext, T, MVT::i1, Promote); } FastISel *WebAssemblyTargetLowering::createFastISel( Index: lib/Target/WebAssembly/WebAssemblyInstrMemory.td =================================================================== --- lib/Target/WebAssembly/WebAssemblyInstrMemory.td +++ lib/Target/WebAssembly/WebAssemblyInstrMemory.td @@ -14,37 +14,84 @@ /* * TODO(jfb): Add the following. - * Each has optional alignment and immediate byte offset. - * - * int32.load_sx[int8]: sign-extend to int32 - * int32.load_sx[int16]: sign-extend to int32 - * int32.load_zx[int8]: zero-extend to int32 - * int32.load_zx[int16]: zero-extend to int32 - * int32.load[int32]: (no conversion) - * int64.load_sx[int8]: sign-extend to int64 - * int64.load_sx[int16]: sign-extend to int64 - * int64.load_sx[int32]: sign-extend to int64 - * int64.load_zx[int8]: zero-extend to int64 - * int64.load_zx[int16]: zero-extend to int64 - * int64.load_zx[int32]: zero-extend to int64 - * int64.load[int64]: (no conversion) - * float32.load[float32]: (no conversion) - * float64.load[float64]: (no conversion) - * - * int32.store[int8]: wrap int32 to int8 - * int32.store[int16]: wrap int32 to int16 - * int32.store[int32]: (no conversion) - * int64.store[int8]: wrap int64 to int8 - * int64.store[int16]: wrap int64 to int16 - * int64.store[int32]: wrap int64 to int32 - * int64.store[int64]: (no conversion) - * float32.store[float32]: (no conversion) - * float64.store[float64]: (no conversion) * * load_global: load the value of a given global variable * store_global: store a given value to a given global variable */ +// FIXME: +// - HasAddr64 +// - WebAssemblyTargetLowering::isLegalAddressingMode +// - WebAssemblyTargetLowering having to do with atomics +// - Each has optional alignment and immediate byte offset. + +// WebAssembly has i8/i16/i32/i64/f32/f64 memory types, but doesn't have i8/i16 +// local types. These memory-only types instead zero- or sign-extend into local +// types when loading, and truncate when storing. + +// Basic load. +def LOAD_I32_ : I<(outs Int32:$dst), (ins Int32:$addr), + [(set Int32:$dst, (load Int32:$addr))]>; +def LOAD_I64_ : I<(outs Int64:$dst), (ins Int32:$addr), + [(set Int64:$dst, (load Int32:$addr))]>; +def LOAD_F32_ : I<(outs Float32:$dst), (ins Int32:$addr), + [(set Float32:$dst, (load Int32:$addr))]>; +def LOAD_F64_ : I<(outs Float64:$dst), (ins Int32:$addr), + [(set Float64:$dst, (load Int32:$addr))]>; + +// Extending load. +def LOAD_SX_I8_I32_ : I<(outs Int32:$dst), (ins Int32:$addr), + [(set Int32:$dst, (sextloadi8 Int32:$addr))]>; +def LOAD_ZX_I8_I32_ : I<(outs Int32:$dst), (ins Int32:$addr), + [(set Int32:$dst, (zextloadi8 Int32:$addr))]>; +def LOAD_SX_I16_I32_ : I<(outs Int32:$dst), (ins Int32:$addr), + [(set Int32:$dst, (sextloadi16 Int32:$addr))]>; +def LOAD_ZX_I16_I32_ : I<(outs Int32:$dst), (ins Int32:$addr), + [(set Int32:$dst, (zextloadi16 Int32:$addr))]>; +def LOAD_SX_I8_I64_ : I<(outs Int64:$dst), (ins Int32:$addr), + [(set Int64:$dst, (sextloadi8 Int32:$addr))]>; +def LOAD_ZX_I8_I64_ : I<(outs Int64:$dst), (ins Int32:$addr), + [(set Int64:$dst, (zextloadi8 Int32:$addr))]>; +def LOAD_SX_I16_I64_ : I<(outs Int64:$dst), (ins Int32:$addr), + [(set Int64:$dst, (sextloadi16 Int32:$addr))]>; +def LOAD_ZX_I16_I64_ : I<(outs Int64:$dst), (ins Int32:$addr), + [(set Int64:$dst, (zextloadi16 Int32:$addr))]>; +def LOAD_SX_I32_I64_ : I<(outs Int64:$dst), (ins Int32:$addr), + [(set Int64:$dst, (sextloadi32 Int32:$addr))]>; +def LOAD_ZX_I32_I64_ : I<(outs Int64:$dst), (ins Int32:$addr), + [(set Int64:$dst, (zextloadi32 Int32:$addr))]>; + +// "Don't care" extending load become zero-extending load. +def : Pat<(i32 (extloadi8 Int32:$addr)), (LOAD_ZX_I8_I32_ $addr)>; +def : Pat<(i32 (extloadi16 Int32:$addr)), (LOAD_ZX_I16_I32_ $addr)>; +def : Pat<(i64 (extloadi8 Int32:$addr)), (LOAD_ZX_I8_I64_ $addr)>; +def : Pat<(i64 (extloadi16 Int32:$addr)), (LOAD_ZX_I16_I64_ $addr)>; +def : Pat<(i64 (extloadi32 Int32:$addr)), (LOAD_ZX_I32_I64_ $addr)>; + +// Basic store. +// Note: WebAssembly inverts SelectionDAG's usual operand order. +def STORE_I32_ : I<(outs), (ins Int32:$addr, Int32:$val), + [(store Int32:$val, Int32:$addr)]>; +def STORE_I64_ : I<(outs), (ins Int32:$addr, Int64:$val), + [(store Int64:$val, Int32:$addr)]>; +def STORE_F32_ : I<(outs), (ins Int32:$addr, Float32:$val), + [(store Float32:$val, Int32:$addr)]>; +def STORE_F64_ : I<(outs), (ins Int32:$addr, Float64:$val), + [(store Float64:$val, Int32:$addr)]>; + +// Truncating store. +def STORE_I8_I32 : I<(outs), (ins Int32:$addr, Int32:$val), + [(truncstorei8 Int32:$val, Int32:$addr)]>; +def STORE_I16_I32 : I<(outs), (ins Int32:$addr, Int32:$val), + [(truncstorei16 Int32:$val, Int32:$addr)]>; +def STORE_I8_I64 : I<(outs), (ins Int32:$addr, Int64:$val), + [(truncstorei8 Int64:$val, Int32:$addr)]>; +def STORE_I16_I64 : I<(outs), (ins Int32:$addr, Int64:$val), + [(truncstorei16 Int64:$val, Int32:$addr)]>; +def STORE_I32_I64 : I<(outs), (ins Int32:$addr, Int64:$val), + [(truncstorei32 Int64:$val, Int32:$addr)]>; + +// Page size. def page_size_I32 : I<(outs Int32:$dst), (ins), [(set Int32:$dst, (int_wasm_page_size))]>, Requires<[HasAddr32]>; Index: test/CodeGen/WebAssembly/load-ext.ll =================================================================== --- /dev/null +++ test/CodeGen/WebAssembly/load-ext.ll @@ -0,0 +1,86 @@ +; RUN: llc < %s -asm-verbose=false | FileCheck %s + +; Test that extending loads are assembled properly. + +target datalayout = "e-p:32:32-i64:64-v128:8:128-n32:64-S128" +target triple = "wasm32-unknown-unknown" + +; CHECK-LABEL: (func $sext_i8_i32 +; CHECK: (setlocal @1 (load_sx_i8_i32 @0)) +define i32 @sext_i8_i32(i8 *%p) { + %v = load i8, i8* %p + %e = sext i8 %v to i32 + ret i32 %e +} + +; CHECK-LABEL: (func $zext_i8_i32 +; CHECK: (setlocal @1 (load_zx_i8_i32 @0)) +define i32 @zext_i8_i32(i8 *%p) { + %v = load i8, i8* %p + %e = zext i8 %v to i32 + ret i32 %e +} + +; CHECK-LABEL: (func $sext_i16_i32 +; CHECK: (setlocal @1 (load_sx_i16_i32 @0)) +define i32 @sext_i16_i32(i16 *%p) { + %v = load i16, i16* %p + %e = sext i16 %v to i32 + ret i32 %e +} + +; CHECK-LABEL: (func $zext_i16_i32 +; CHECK: (setlocal @1 (load_zx_i16_i32 @0)) +define i32 @zext_i16_i32(i16 *%p) { + %v = load i16, i16* %p + %e = zext i16 %v to i32 + ret i32 %e +} + +; CHECK-LABEL: (func $sext_i8_i64 +; CHECK: (setlocal @1 (load_sx_i8_i64 @0)) +define i64 @sext_i8_i64(i8 *%p) { + %v = load i8, i8* %p + %e = sext i8 %v to i64 + ret i64 %e +} + +; CHECK-LABEL: (func $zext_i8_i64 +; CHECK: (setlocal @1 (load_zx_i8_i64 @0)) +define i64 @zext_i8_i64(i8 *%p) { + %v = load i8, i8* %p + %e = zext i8 %v to i64 + ret i64 %e +} + +; CHECK-LABEL: (func $sext_i16_i64 +; CHECK: (setlocal @1 (load_sx_i16_i64 @0)) +define i64 @sext_i16_i64(i16 *%p) { + %v = load i16, i16* %p + %e = sext i16 %v to i64 + ret i64 %e +} + +; CHECK-LABEL: (func $zext_i16_i64 +; CHECK: (setlocal @1 (load_zx_i16_i64 @0)) +define i64 @zext_i16_i64(i16 *%p) { + %v = load i16, i16* %p + %e = zext i16 %v to i64 + ret i64 %e +} + +; CHECK-LABEL: (func $sext_i32_i64 +; CHECK: (setlocal @1 (load_sx_i32_i64 @0)) +define i64 @sext_i32_i64(i32 *%p) { + %v = load i32, i32* %p + %e = sext i32 %v to i64 + ret i64 %e +} + +; CHECK-LABEL: (func $zext_i32_i64 +; CHECK: (setlocal @1 (load_zx_i32_i64 @0)) +define i64 @zext_i32_i64(i32 *%p) { + %v = load i32, i32* %p + %e = zext i32 %v to i64 + ret i64 %e +} Index: test/CodeGen/WebAssembly/load-store-i1.ll =================================================================== --- /dev/null +++ test/CodeGen/WebAssembly/load-store-i1.ll @@ -0,0 +1,68 @@ +; RUN: llc < %s -asm-verbose=false | FileCheck %s + +; Test that i1 extending loads and truncating stores are assembled properly. + +target datalayout = "e-p:32:32-i64:64-v128:8:128-n32:64-S128" +target triple = "wasm32-unknown-unknown" + +; CHECK-LABEL: (func $load_unsigned_i1_i32 +; CHECK: (setlocal @1 (load_zx_i8_i32 @0)) +; CHECK-NEXT: (return @1) +define i32 @load_unsigned_i1_i32(i1* %p) { + %v = load i1, i1* %p + %e = zext i1 %v to i32 + ret i32 %e +} + +; CHECK-LABEL: (func $load_signed_i1_i32 +; CHECK: (setlocal @1 (load_zx_i8_i32 @0)) +; CHECK-NEXT: (setlocal @2 (immediate 31)) +; CHECK-NEXT: (setlocal @3 (shl @1 @2)) +; CHECK-NEXT: (setlocal @4 (sar @3 @2)) +; CHECK-NEXT: (return @4) +define i32 @load_signed_i1_i32(i1* %p) { + %v = load i1, i1* %p + %e = sext i1 %v to i32 + ret i32 %e +} + +; CHECK-LABEL: (func $load_unsigned_i1_i64 +; CHECK: (setlocal @1 (load_zx_i8_i64 @0)) +; CHECK-NEXT: (return @1) +define i64 @load_unsigned_i1_i64(i1* %p) { + %v = load i1, i1* %p + %e = zext i1 %v to i64 + ret i64 %e +} + +; CHECK-LABEL: (func $load_signed_i1_i64 +; CHECK: (setlocal @1 (load_zx_i8_i64 @0)) +; CHECK-NEXT: (setlocal @2 (immediate 63)) +; CHECK-NEXT: (setlocal @3 (shl @1 @2)) +; CHECK-NEXT: (setlocal @4 (sar @3 @2)) +; CHECK-NEXT: (return @4) +define i64 @load_signed_i1_i64(i1* %p) { + %v = load i1, i1* %p + %e = sext i1 %v to i64 + ret i64 %e +} + +; CHECK-LABEL: (func $store_i32_i1 +; CHECK: (setlocal @2 (immediate 1)) +; CHECK-NEXT: (setlocal @3 (and @1 @2)) +; CHECK-NEXT: (store_i8 @0 @3) +define void @store_i32_i1(i1* %p, i32 %v) { + %t = trunc i32 %v to i1 + store i1 %t, i1* %p + ret void +} + +; CHECK-LABEL: (func $store_i64_i1 +; CHECK: (setlocal @2 (immediate 1)) +; CHECK-NEXT: (setlocal @3 (and @1 @2)) +; CHECK-NEXT: (store_i8 @0 @3) +define void @store_i64_i1(i1* %p, i64 %v) { + %t = trunc i64 %v to i1 + store i1 %t, i1* %p + ret void +} Index: test/CodeGen/WebAssembly/load.ll =================================================================== --- /dev/null +++ test/CodeGen/WebAssembly/load.ll @@ -0,0 +1,46 @@ +; RUN: llc < %s -asm-verbose=false | FileCheck %s + +; Test that basic loads are assembled properly. + +target datalayout = "e-p:32:32-i64:64-v128:8:128-n32:64-S128" +target triple = "wasm32-unknown-unknown" + +; CHECK-LABEL: (func $ldi32 +; CHECK-NEXT: (param i32) (result i32) +; CHECK-NEXT: (setlocal @0 (argument 0)) +; CHECK-NEXT: (setlocal @1 (load_i32 @0)) +; CHECK-NEXT: (return @1) +define i32 @ldi32(i32 *%p) { + %v = load i32, i32* %p + ret i32 %v +} + +; CHECK-LABEL: (func $ldi64 +; CHECK-NEXT: (param i32) (result i64) +; CHECK-NEXT: (setlocal @0 (argument 0)) +; CHECK-NEXT: (setlocal @1 (load_i64 @0)) +; CHECK-NEXT: (return @1) +define i64 @ldi64(i64 *%p) { + %v = load i64, i64* %p + ret i64 %v +} + +; CHECK-LABEL: (func $ldf32 +; CHECK-NEXT: (param i32) (result f32) +; CHECK-NEXT: (setlocal @0 (argument 0)) +; CHECK-NEXT: (setlocal @1 (load_f32 @0)) +; CHECK-NEXT: (return @1) +define float @ldf32(float *%p) { + %v = load float, float* %p + ret float %v +} + +; CHECK-LABEL: (func $ldf64 +; CHECK-NEXT: (param i32) (result f64) +; CHECK-NEXT: (setlocal @0 (argument 0)) +; CHECK-NEXT: (setlocal @1 (load_f64 @0)) +; CHECK-NEXT: (return @1) +define double @ldf64(double *%p) { + %v = load double, double* %p + ret double %v +} Index: test/CodeGen/WebAssembly/store-trunc.ll =================================================================== --- /dev/null +++ test/CodeGen/WebAssembly/store-trunc.ll @@ -0,0 +1,46 @@ +; RUN: llc < %s -asm-verbose=false | FileCheck %s + +; Test that truncating stores are assembled properly. + +target datalayout = "e-p:32:32-i64:64-v128:8:128-n32:64-S128" +target triple = "wasm32-unknown-unknown" + +; CHECK-LABEL: (func $trunc_i8_i32 +; CHECK: (store_i8 @1 @0) +define void @trunc_i8_i32(i8 *%p, i32 %v) { + %t = trunc i32 %v to i8 + store i8 %t, i8* %p + ret void +} + +; CHECK-LABEL: (func $trunc_i16_i32 +; CHECK: (store_i16 @1 @0) +define void @trunc_i16_i32(i16 *%p, i32 %v) { + %t = trunc i32 %v to i16 + store i16 %t, i16* %p + ret void +} + +; CHECK-LABEL: (func $trunc_i8_i64 +; CHECK: (store_i8 @1 @0) +define void @trunc_i8_i64(i8 *%p, i64 %v) { + %t = trunc i64 %v to i8 + store i8 %t, i8* %p + ret void +} + +; CHECK-LABEL: (func $trunc_i16_i64 +; CHECK: (store_i16 @1 @0) +define void @trunc_i16_i64(i16 *%p, i64 %v) { + %t = trunc i64 %v to i16 + store i16 %t, i16* %p + ret void +} + +; CHECK-LABEL: (func $trunc_i32_i64 +; CHECK: (store_i32 @1 @0) +define void @trunc_i32_i64(i32 *%p, i64 %v) { + %t = trunc i64 %v to i32 + store i32 %t, i32* %p + ret void +} Index: test/CodeGen/WebAssembly/store.ll =================================================================== --- /dev/null +++ test/CodeGen/WebAssembly/store.ll @@ -0,0 +1,50 @@ +; RUN: llc < %s -asm-verbose=false | FileCheck %s + +; Test that basic stores are assembled properly. + +target datalayout = "e-p:32:32-i64:64-v128:8:128-n32:64-S128" +target triple = "wasm32-unknown-unknown" + +; CHECK-LABEL: (func $sti32 +; CHECK-NEXT: (param i32) (param i32) +; CHECK-NEXT: (setlocal @0 (argument 1)) +; CHECK-NEXT: (setlocal @1 (argument 0)) +; CHECK-NEXT: (store_i32 @1 @0) +; CHECK-NEXT: (return) +define void @sti32(i32 *%p, i32 %v) { + store i32 %v, i32* %p + ret void +} + +; CHECK-LABEL: (func $sti64 +; CHECK-NEXT: (param i32) (param i64) +; CHECK-NEXT: (setlocal @0 (argument 1)) +; CHECK-NEXT: (setlocal @1 (argument 0)) +; CHECK-NEXT: (store_i64 @1 @0) +; CHECK-NEXT: (return) +define void @sti64(i64 *%p, i64 %v) { + store i64 %v, i64* %p + ret void +} + +; CHECK-LABEL: (func $stf32 +; CHECK-NEXT: (param i32) (param f32) +; CHECK-NEXT: (setlocal @0 (argument 1)) +; CHECK-NEXT: (setlocal @1 (argument 0)) +; CHECK-NEXT: (store_f32 @1 @0) +; CHECK-NEXT: (return) +define void @stf32(float *%p, float %v) { + store float %v, float* %p + ret void +} + +; CHECK-LABEL: (func $stf64 +; CHECK-NEXT: (param i32) (param f64) +; CHECK-NEXT: (setlocal @0 (argument 1)) +; CHECK-NEXT: (setlocal @1 (argument 0)) +; CHECK-NEXT: (store_f64 @1 @0) +; CHECK-NEXT: (return) +define void @stf64(double *%p, double %v) { + store double %v, double* %p + ret void +}