https://github.com/MaxDesiatov updated https://github.com/llvm/llvm-project/pull/212007
>From 2b56c5300174cd084b4dca696899bd1dea04b9cd Mon Sep 17 00:00:00 2001 From: Max Desiatov <[email protected]> Date: Sat, 25 Jul 2026 10:50:33 +0100 Subject: [PATCH 1/2] [WebAssembly] Emit constant initializers for addrspace(1) globals A Wasm global in `addrspace(1)` lost its initializer, because the object writer hardcoded 0 into the Global section's init expression. This carries the constant's raw bits on `MCSymbolWasm` so the writer emits the real i32/i64/f32/f64 value. Added `llvm/test/CodeGen/WebAssembly/global-init.ll`, `clang/test/CodeGen/WebAssembly/wasm-globals.c`, and `lld/test/wasm/global-init.ll`. # Conflicts: # llvm/lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp --- clang/test/CodeGen/WebAssembly/wasm-globals.c | 43 ++++++++++++++++++ lld/test/wasm/global-init.ll | 26 +++++++++++ llvm/include/llvm/MC/MCSymbolWasm.h | 8 ++++ llvm/lib/MC/WasmObjectWriter.cpp | 14 ++++-- .../WebAssembly/WebAssemblyAsmPrinter.cpp | 32 +++++++++++++- llvm/test/CodeGen/WebAssembly/global-init.ll | 44 +++++++++++++++++++ 6 files changed, 161 insertions(+), 6 deletions(-) create mode 100644 clang/test/CodeGen/WebAssembly/wasm-globals.c create mode 100644 lld/test/wasm/global-init.ll create mode 100644 llvm/test/CodeGen/WebAssembly/global-init.ll diff --git a/clang/test/CodeGen/WebAssembly/wasm-globals.c b/clang/test/CodeGen/WebAssembly/wasm-globals.c new file mode 100644 index 0000000000000..31378f0c862c2 --- /dev/null +++ b/clang/test/CodeGen/WebAssembly/wasm-globals.c @@ -0,0 +1,43 @@ +// REQUIRES: webassembly-registered-target +// RUN: %clang_cc1 -triple wasm32-unknown-unknown -mrelocation-model static -O2 -emit-llvm -o - %s | FileCheck %s --check-prefix=IR +// RUN: %clang_cc1 -triple wasm32-unknown-unknown -mrelocation-model static -O2 -S -o - %s | FileCheck %s --check-prefix=ASM +// RUN: %clang_cc1 -triple wasm32-unknown-unknown -mrelocation-model static -O2 -emit-obj -o %t.o %s +// RUN: obj2yaml %t.o | FileCheck %s --check-prefix=OBJ + +// IR: @mut = {{.*}}addrspace(1) global i32 33554435 +int mut __attribute__((address_space(1))) = 0x02000003; + +// Without volatile the load of a constant global folds to i32.const, hiding +// the global.get. +// IR: @immut = {{.*}}addrspace(1) constant i32 42 +const volatile int immut __attribute__((address_space(1))) = 42; + +// ASM-LABEL: read_mut: +// ASM: global.get mut{{$}} +int read_mut(void) { return mut; } + +// ASM-LABEL: write_mut: +// ASM: global.set mut{{$}} +void write_mut(int x) { mut = x; } + +// ASM-LABEL: read_immut: +// ASM: global.get immut{{$}} +int read_immut(void) { return immut; } + +// ASM: .globaltype mut, i32{{$}} +// ASM: .globaltype immut, i32, immutable{{$}} + +// OBJ: - Type: GLOBAL +// OBJ-NEXT: Globals: +// OBJ-NEXT: - Index: 0 +// OBJ-NEXT: Type: I32 +// OBJ-NEXT: Mutable: true +// OBJ-NEXT: InitExpr: +// OBJ-NEXT: Opcode: I32_CONST +// OBJ-NEXT: Value: 33554435 +// OBJ-NEXT: - Index: 1 +// OBJ-NEXT: Type: I32 +// OBJ-NEXT: Mutable: false +// OBJ-NEXT: InitExpr: +// OBJ-NEXT: Opcode: I32_CONST +// OBJ-NEXT: Value: 42 diff --git a/lld/test/wasm/global-init.ll b/lld/test/wasm/global-init.ll new file mode 100644 index 0000000000000..b6f1be2682dce --- /dev/null +++ b/lld/test/wasm/global-init.ll @@ -0,0 +1,26 @@ +; RUN: llc -filetype=obj -mtriple=wasm32-unknown-unknown %s -o %t.o +; RUN: wasm-ld --no-entry --export=use --no-gc-sections %t.o -o %t.wasm +; RUN: obj2yaml %t.wasm | FileCheck %s + +; The constant initializer of an address-space-1 Wasm global must survive +; linking, and the global's name must appear in the "name" custom section. + +@gv = hidden addrspace(1) global i32 33554435 +define i32 @use() { + %v = load i32, ptr addrspace(1) @gv + ret i32 %v +} + +; CHECK: - Type: GLOBAL +; CHECK: - Index: 1 +; CHECK-NEXT: Type: I32 +; CHECK-NEXT: Mutable: true +; CHECK-NEXT: InitExpr: +; CHECK-NEXT: Opcode: I32_CONST +; CHECK-NEXT: Value: 33554435 + +; CHECK: - Type: CUSTOM +; CHECK: Name: name +; CHECK: GlobalNames: +; CHECK: - Index: 1 +; CHECK: Name: gv diff --git a/llvm/include/llvm/MC/MCSymbolWasm.h b/llvm/include/llvm/MC/MCSymbolWasm.h index 5c9f14e5e6d64..e18dbc29ac786 100644 --- a/llvm/include/llvm/MC/MCSymbolWasm.h +++ b/llvm/include/llvm/MC/MCSymbolWasm.h @@ -28,6 +28,7 @@ class MCSymbolWasm : public MCSymbol { wasm::WasmSignature *Signature = nullptr; std::optional<wasm::WasmGlobalType> GlobalType; std::optional<wasm::WasmTableType> TableType; + std::optional<uint64_t> GlobalInitValue; /// An expression describing how to calculate the size of a symbol. If a /// symbol has no size this field will be NULL. @@ -138,6 +139,13 @@ class MCSymbolWasm : public MCSymbol { } void setGlobalType(wasm::WasmGlobalType GT) { GlobalType = GT; } + bool hasGlobalInitValue() const { return GlobalInitValue.has_value(); } + uint64_t getGlobalInitValue() const { + assert(GlobalInitValue); + return *GlobalInitValue; + } + void setGlobalInitValue(uint64_t V) { GlobalInitValue = V; } + bool hasTableType() const { return TableType.has_value(); } const wasm::WasmTableType &getTableType() const { assert(hasTableType()); diff --git a/llvm/lib/MC/WasmObjectWriter.cpp b/llvm/lib/MC/WasmObjectWriter.cpp index 43f71cc43bf8f..f8b1dd174e8c7 100644 --- a/llvm/lib/MC/WasmObjectWriter.cpp +++ b/llvm/lib/MC/WasmObjectWriter.cpp @@ -919,16 +919,16 @@ void WasmObjectWriter::writeGlobalSection(ArrayRef<wasm::WasmGlobal> Globals) { W->OS << char(Global.InitExpr.Inst.Opcode); switch (Global.Type.Type) { case wasm::WASM_TYPE_I32: - encodeSLEB128(0, W->OS); + encodeSLEB128(Global.InitExpr.Inst.Value.Int32, W->OS); break; case wasm::WASM_TYPE_I64: - encodeSLEB128(0, W->OS); + encodeSLEB128(Global.InitExpr.Inst.Value.Int64, W->OS); break; case wasm::WASM_TYPE_F32: - writeI32(0); + writeI32(Global.InitExpr.Inst.Value.Float32); break; case wasm::WASM_TYPE_F64: - writeI64(0); + writeI64(Global.InitExpr.Inst.Value.Float64); break; case wasm::WASM_TYPE_EXTERNREF: writeValueType(wasm::ValType::EXTERNREF); @@ -1666,18 +1666,24 @@ uint64_t WasmObjectWriter::writeOneObject(MCAssembler &Asm, Global.Type = WS.getGlobalType(); Global.Index = NumGlobalImports + Globals.size(); Global.InitExpr.Extended = false; + uint64_t Bits = WS.hasGlobalInitValue() ? WS.getGlobalInitValue() : 0; switch (Global.Type.Type) { case wasm::WASM_TYPE_I32: Global.InitExpr.Inst.Opcode = wasm::WASM_OPCODE_I32_CONST; + Global.InitExpr.Inst.Value.Int32 = + static_cast<int32_t>(static_cast<uint32_t>(Bits)); break; case wasm::WASM_TYPE_I64: Global.InitExpr.Inst.Opcode = wasm::WASM_OPCODE_I64_CONST; + Global.InitExpr.Inst.Value.Int64 = static_cast<int64_t>(Bits); break; case wasm::WASM_TYPE_F32: Global.InitExpr.Inst.Opcode = wasm::WASM_OPCODE_F32_CONST; + Global.InitExpr.Inst.Value.Float32 = static_cast<uint32_t>(Bits); break; case wasm::WASM_TYPE_F64: Global.InitExpr.Inst.Opcode = wasm::WASM_OPCODE_F64_CONST; + Global.InitExpr.Inst.Value.Float64 = Bits; break; case wasm::WASM_TYPE_EXTERNREF: Global.InitExpr.Inst.Opcode = wasm::WASM_OPCODE_REF_NULL; diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp index 84bab9487f0c8..26e224a597540 100644 --- a/llvm/lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp +++ b/llvm/lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp @@ -41,6 +41,7 @@ #include "llvm/CodeGen/MachineModuleInfoImpls.h" #include "llvm/CodeGen/MachinePassManager.h" #include "llvm/IR/Analysis.h" +#include "llvm/IR/Constants.h" #include "llvm/IR/DataLayout.h" #include "llvm/IR/DebugInfoMetadata.h" #include "llvm/IR/GlobalVariable.h" @@ -189,6 +190,26 @@ MCSymbolWasm *WebAssemblyAsmPrinter::getMCSymbolForFunction( return WasmSym; } +static std::optional<uint64_t> getWasmGlobalInitBits(const Constant *C) { + // Sub-i32 ints and half/bfloat legalize to a single i32/f32 MVT and so pass + // wasmSymbolSetType(), but their bits are not an i32/f32 init value; gate on + // the exact IR type so they fall back to the default 0 rather than emit a + // garbage init expr. + if (const auto *CI = dyn_cast<ConstantInt>(C)) { + unsigned BW = CI->getType()->getIntegerBitWidth(); + if (BW == 32 || BW == 64) + return CI->getValue().getZExtValue(); + return std::nullopt; + } + if (const auto *CF = dyn_cast<ConstantFP>(C)) { + Type *T = CF->getType(); + if (T->isFloatTy() || T->isDoubleTy()) + return CF->getValueAPF().bitcastToAPInt().getZExtValue(); + return std::nullopt; + } + return std::nullopt; +} + void WebAssemblyAsmPrinter::emitGlobalVariable(const GlobalVariable *GV) { if (GV->hasAttribute("wasm-import-module") || GV->hasAttribute("wasm-import-name")) { @@ -236,6 +257,15 @@ void WebAssemblyAsmPrinter::emitGlobalVariable(const GlobalVariable *GV) { } emitVisibility(Sym, GV->getVisibility(), !GV->isDeclaration()); + + // WasmObjectWriter reads these init bits back off the symbol to emit the init + // expression instead of the type's default 0. + if (GV->hasInitializer() && Sym->isGlobal()) { + if (std::optional<uint64_t> Bits = + getWasmGlobalInitBits(GV->getInitializer())) + Sym->setGlobalInitValue(*Bits); + } + emitSymbolType(Sym); if (GV->isDeclaration()) { if (GV->hasAttribute("wasm-import-module")) { @@ -261,8 +291,6 @@ void WebAssemblyAsmPrinter::emitGlobalVariable(const GlobalVariable *GV) { Sym->setExportName(OutContext.allocateString(ExportName)); getTargetStreamer()->emitExportName(Sym, ExportName); } - // TODO: Actually emit the initializer value. Otherwise the global has the - // default value for its type (0, ref.null, etc). OutStreamer->addBlankLine(); } } diff --git a/llvm/test/CodeGen/WebAssembly/global-init.ll b/llvm/test/CodeGen/WebAssembly/global-init.ll new file mode 100644 index 0000000000000..72e5c180c649c --- /dev/null +++ b/llvm/test/CodeGen/WebAssembly/global-init.ll @@ -0,0 +1,44 @@ +; RUN: llc < %s -mtriple=wasm32-unknown-unknown -filetype=obj | obj2yaml | FileCheck %s + +; No function is defined here, so the AsmPrinter's Subtarget is null: emitting +; these address-space-1 globals must not crash, and each initializer must reach +; the Global section instead of the type's default 0. + +@i32v = addrspace(1) global i32 33554435 +@i64v = addrspace(1) global i64 8589934595 +@f32v = addrspace(1) global float 2.0 +@f64v = addrspace(1) global double 2.0 +@constv = addrspace(1) constant i32 42 + +; CHECK: - Type: GLOBAL +; CHECK-NEXT: Globals: +; CHECK-NEXT: - Index: 0 +; CHECK-NEXT: Type: I32 +; CHECK-NEXT: Mutable: true +; CHECK-NEXT: InitExpr: +; CHECK-NEXT: Opcode: I32_CONST +; CHECK-NEXT: Value: 33554435 +; CHECK-NEXT: - Index: 1 +; CHECK-NEXT: Type: I64 +; CHECK-NEXT: Mutable: true +; CHECK-NEXT: InitExpr: +; CHECK-NEXT: Opcode: I64_CONST +; CHECK-NEXT: Value: 8589934595 +; CHECK-NEXT: - Index: 2 +; CHECK-NEXT: Type: F32 +; CHECK-NEXT: Mutable: true +; CHECK-NEXT: InitExpr: +; CHECK-NEXT: Opcode: F32_CONST +; CHECK-NEXT: Value: 1073741824 +; CHECK-NEXT: - Index: 3 +; CHECK-NEXT: Type: F64 +; CHECK-NEXT: Mutable: true +; CHECK-NEXT: InitExpr: +; CHECK-NEXT: Opcode: F64_CONST +; CHECK-NEXT: Value: 4611686018427387904 +; CHECK-NEXT: - Index: 4 +; CHECK-NEXT: Type: I32 +; CHECK-NEXT: Mutable: false +; CHECK-NEXT: InitExpr: +; CHECK-NEXT: Opcode: I32_CONST +; CHECK-NEXT: Value: 42 >From c931291759acfe6bd264b69c151ab6aa21be14fc Mon Sep 17 00:00:00 2001 From: Max Desiatov <[email protected]> Date: Sun, 11 Oct 2026 22:19:42 +0100 Subject: [PATCH 2/2] [WebAssembly] Add a .globalinit directive and diagnose unsupported global initializers Store the value as a `wasm::WasmInitExpr` on `MCSymbolWasm`, which the asm streamer prints and the asm parser reads as `.globalinit SYM, <type>.const VALUE`. An unsupported initializer now triggers an error. --- clang/test/CodeGen/WebAssembly/wasm-globals.c | 19 +- lld/test/wasm/global-init.ll | 7 +- llvm/include/llvm/MC/MCSymbolWasm.h | 14 +- llvm/lib/MC/WasmObjectWriter.cpp | 63 ++-- .../AsmParser/WebAssemblyAsmParser.cpp | 170 ++++++++++- .../MCTargetDesc/WebAssemblyInstPrinter.cpp | 30 +- .../WebAssemblyMCTypeUtilities.cpp | 53 ++++ .../MCTargetDesc/WebAssemblyMCTypeUtilities.h | 16 + .../WebAssemblyTargetStreamer.cpp | 31 ++ .../MCTargetDesc/WebAssemblyTargetStreamer.h | 5 + .../WebAssembly/WebAssemblyAsmPrinter.cpp | 70 +++-- .../CodeGen/WebAssembly/global-addrspace.ll | 5 + .../WebAssembly/global-init-invalid.ll | 35 +++ .../WebAssembly/global-init-reftype.ll | 22 ++ llvm/test/CodeGen/WebAssembly/global-init.ll | 163 ++++++++--- .../WebAssembly/global-unsupported-type.ll | 12 + llvm/test/MC/WebAssembly/globalinit-invalid.s | 77 +++++ .../MC/WebAssembly/globalinit-not-global.s | 32 ++ .../MC/WebAssembly/globalinit-undefined.s | 22 ++ llvm/test/MC/WebAssembly/globalinit.s | 274 ++++++++++++++++++ .../MC/WebAssembly/globaltype-unsupported.s | 8 + 21 files changed, 989 insertions(+), 139 deletions(-) create mode 100644 llvm/test/CodeGen/WebAssembly/global-init-invalid.ll create mode 100644 llvm/test/CodeGen/WebAssembly/global-init-reftype.ll create mode 100644 llvm/test/CodeGen/WebAssembly/global-unsupported-type.ll create mode 100644 llvm/test/MC/WebAssembly/globalinit-invalid.s create mode 100644 llvm/test/MC/WebAssembly/globalinit-not-global.s create mode 100644 llvm/test/MC/WebAssembly/globalinit-undefined.s create mode 100644 llvm/test/MC/WebAssembly/globalinit.s create mode 100644 llvm/test/MC/WebAssembly/globaltype-unsupported.s diff --git a/clang/test/CodeGen/WebAssembly/wasm-globals.c b/clang/test/CodeGen/WebAssembly/wasm-globals.c index 31378f0c862c2..24437005523d8 100644 --- a/clang/test/CodeGen/WebAssembly/wasm-globals.c +++ b/clang/test/CodeGen/WebAssembly/wasm-globals.c @@ -1,8 +1,6 @@ // REQUIRES: webassembly-registered-target // RUN: %clang_cc1 -triple wasm32-unknown-unknown -mrelocation-model static -O2 -emit-llvm -o - %s | FileCheck %s --check-prefix=IR // RUN: %clang_cc1 -triple wasm32-unknown-unknown -mrelocation-model static -O2 -S -o - %s | FileCheck %s --check-prefix=ASM -// RUN: %clang_cc1 -triple wasm32-unknown-unknown -mrelocation-model static -O2 -emit-obj -o %t.o %s -// RUN: obj2yaml %t.o | FileCheck %s --check-prefix=OBJ // IR: @mut = {{.*}}addrspace(1) global i32 33554435 int mut __attribute__((address_space(1))) = 0x02000003; @@ -25,19 +23,6 @@ void write_mut(int x) { mut = x; } int read_immut(void) { return immut; } // ASM: .globaltype mut, i32{{$}} +// ASM-NEXT: .globalinit mut, i32.const 33554435{{$}} // ASM: .globaltype immut, i32, immutable{{$}} - -// OBJ: - Type: GLOBAL -// OBJ-NEXT: Globals: -// OBJ-NEXT: - Index: 0 -// OBJ-NEXT: Type: I32 -// OBJ-NEXT: Mutable: true -// OBJ-NEXT: InitExpr: -// OBJ-NEXT: Opcode: I32_CONST -// OBJ-NEXT: Value: 33554435 -// OBJ-NEXT: - Index: 1 -// OBJ-NEXT: Type: I32 -// OBJ-NEXT: Mutable: false -// OBJ-NEXT: InitExpr: -// OBJ-NEXT: Opcode: I32_CONST -// OBJ-NEXT: Value: 42 +// ASM-NEXT: .globalinit immut, i32.const 42{{$}} diff --git a/lld/test/wasm/global-init.ll b/lld/test/wasm/global-init.ll index b6f1be2682dce..9c3582b0da310 100644 --- a/lld/test/wasm/global-init.ll +++ b/lld/test/wasm/global-init.ll @@ -1,11 +1,14 @@ -; RUN: llc -filetype=obj -mtriple=wasm32-unknown-unknown %s -o %t.o +; RUN: llc -filetype=obj %s -o %t.o ; RUN: wasm-ld --no-entry --export=use --no-gc-sections %t.o -o %t.wasm ; RUN: obj2yaml %t.wasm | FileCheck %s ; The constant initializer of an address-space-1 Wasm global must survive ; linking, and the global's name must appear in the "name" custom section. +target triple = "wasm32-unknown-unknown" + @gv = hidden addrspace(1) global i32 33554435 + define i32 @use() { %v = load i32, ptr addrspace(1) @gv ret i32 %v @@ -23,4 +26,4 @@ define i32 @use() { ; CHECK: Name: name ; CHECK: GlobalNames: ; CHECK: - Index: 1 -; CHECK: Name: gv +; CHECK-NEXT: Name: gv diff --git a/llvm/include/llvm/MC/MCSymbolWasm.h b/llvm/include/llvm/MC/MCSymbolWasm.h index e18dbc29ac786..25f1eec733b68 100644 --- a/llvm/include/llvm/MC/MCSymbolWasm.h +++ b/llvm/include/llvm/MC/MCSymbolWasm.h @@ -28,7 +28,8 @@ class MCSymbolWasm : public MCSymbol { wasm::WasmSignature *Signature = nullptr; std::optional<wasm::WasmGlobalType> GlobalType; std::optional<wasm::WasmTableType> TableType; - std::optional<uint64_t> GlobalInitValue; + /// A global's initial value, or its type's default when unset. + std::optional<wasm::WasmInitExpr> GlobalInitExpr; /// An expression describing how to calculate the size of a symbol. If a /// symbol has no size this field will be NULL. @@ -133,18 +134,19 @@ class MCSymbolWasm : public MCSymbol { const wasm::WasmSignature *getSignature() const { return Signature; } void setSignature(wasm::WasmSignature *Sig) { Signature = Sig; } + bool hasGlobalType() const { return GlobalType.has_value(); } const wasm::WasmGlobalType &getGlobalType() const { assert(GlobalType); return *GlobalType; } void setGlobalType(wasm::WasmGlobalType GT) { GlobalType = GT; } - bool hasGlobalInitValue() const { return GlobalInitValue.has_value(); } - uint64_t getGlobalInitValue() const { - assert(GlobalInitValue); - return *GlobalInitValue; + bool hasGlobalInitExpr() const { return GlobalInitExpr.has_value(); } + const wasm::WasmInitExpr &getGlobalInitExpr() const { + assert(GlobalInitExpr); + return *GlobalInitExpr; } - void setGlobalInitValue(uint64_t V) { GlobalInitValue = V; } + void setGlobalInitExpr(wasm::WasmInitExpr IE) { GlobalInitExpr = IE; } bool hasTableType() const { return TableType.has_value(); } const wasm::WasmTableType &getTableType() const { diff --git a/llvm/lib/MC/WasmObjectWriter.cpp b/llvm/lib/MC/WasmObjectWriter.cpp index f8b1dd174e8c7..e900ca78af009 100644 --- a/llvm/lib/MC/WasmObjectWriter.cpp +++ b/llvm/lib/MC/WasmObjectWriter.cpp @@ -73,12 +73,6 @@ struct WasmFunction { MCSection *Section; }; -// A wasm global to be written into the global section. -struct WasmGlobal { - wasm::WasmGlobalType Type; - uint64_t InitialValue; -}; - // Information about a single item which is part of a COMDAT. For each data // segment or function which is in the COMDAT, there is a corresponding // WasmComdatEntry. @@ -322,6 +316,8 @@ class WasmObjectWriter : public MCObjectWriter { void writeValueType(wasm::ValType Ty) { W->OS << static_cast<char>(Ty); } + void reportUnsupportedGlobal(const MCSymbolWasm &WS, uint8_t Type); + void writeTypeSection(ArrayRef<wasm::WasmSignature> Signatures); void writeImportSection(ArrayRef<wasm::WasmImport> Imports, uint64_t DataSize, uint32_t NumElements); @@ -902,6 +898,16 @@ void WasmObjectWriter::writeTagSection(ArrayRef<uint32_t> TagTypes) { endSection(Section); } +void WasmObjectWriter::reportUnsupportedGlobal(const MCSymbolWasm &WS, + uint8_t Type) { + // llvm/lib/MC cannot call the WebAssembly target's typeToString. + getContext().reportError(SMLoc(), + "WebAssembly global '" + WS.getName() + + "' has a type that cannot be initialized " + "(type 0x" + + Twine::utohexstr(Type) + ")"); +} + void WasmObjectWriter::writeGlobalSection(ArrayRef<wasm::WasmGlobal> Globals) { if (Globals.empty()) return; @@ -917,24 +923,24 @@ void WasmObjectWriter::writeGlobalSection(ArrayRef<wasm::WasmGlobal> Globals) { llvm_unreachable("extected init expressions not supported"); } else { W->OS << char(Global.InitExpr.Inst.Opcode); - switch (Global.Type.Type) { - case wasm::WASM_TYPE_I32: + switch (Global.InitExpr.Inst.Opcode) { + case wasm::WASM_OPCODE_I32_CONST: encodeSLEB128(Global.InitExpr.Inst.Value.Int32, W->OS); break; - case wasm::WASM_TYPE_I64: + case wasm::WASM_OPCODE_I64_CONST: encodeSLEB128(Global.InitExpr.Inst.Value.Int64, W->OS); break; - case wasm::WASM_TYPE_F32: + case wasm::WASM_OPCODE_F32_CONST: writeI32(Global.InitExpr.Inst.Value.Float32); break; - case wasm::WASM_TYPE_F64: + case wasm::WASM_OPCODE_F64_CONST: writeI64(Global.InitExpr.Inst.Value.Float64); break; - case wasm::WASM_TYPE_EXTERNREF: - writeValueType(wasm::ValType::EXTERNREF); + case wasm::WASM_OPCODE_REF_NULL: + writeValueType(static_cast<wasm::ValType>(Global.Type.Type)); break; default: - llvm_unreachable("unexpected type"); + llvm_unreachable("unexpected init expression opcode"); } } W->OS << char(wasm::WASM_OPCODE_END); @@ -1386,6 +1392,11 @@ void WasmObjectWriter::prepareImports( } else if (WS.isGlobal()) { if (WS.isWeak()) report_fatal_error("undefined global symbol cannot be weak"); + if (WS.hasGlobalInitExpr()) + getContext().reportError(SMLoc(), "WebAssembly global '" + + WS.getName() + + "' has an initial value but " + "is not defined"); wasm::WasmImport Import; Import.Field = WS.getImportName(); @@ -1666,30 +1677,40 @@ uint64_t WasmObjectWriter::writeOneObject(MCAssembler &Asm, Global.Type = WS.getGlobalType(); Global.Index = NumGlobalImports + Globals.size(); Global.InitExpr.Extended = false; - uint64_t Bits = WS.hasGlobalInitValue() ? WS.getGlobalInitValue() : 0; switch (Global.Type.Type) { case wasm::WASM_TYPE_I32: Global.InitExpr.Inst.Opcode = wasm::WASM_OPCODE_I32_CONST; - Global.InitExpr.Inst.Value.Int32 = - static_cast<int32_t>(static_cast<uint32_t>(Bits)); + Global.InitExpr.Inst.Value.Int32 = 0; break; case wasm::WASM_TYPE_I64: Global.InitExpr.Inst.Opcode = wasm::WASM_OPCODE_I64_CONST; - Global.InitExpr.Inst.Value.Int64 = static_cast<int64_t>(Bits); + Global.InitExpr.Inst.Value.Int64 = 0; break; case wasm::WASM_TYPE_F32: Global.InitExpr.Inst.Opcode = wasm::WASM_OPCODE_F32_CONST; - Global.InitExpr.Inst.Value.Float32 = static_cast<uint32_t>(Bits); + Global.InitExpr.Inst.Value.Float32 = 0; break; case wasm::WASM_TYPE_F64: Global.InitExpr.Inst.Opcode = wasm::WASM_OPCODE_F64_CONST; - Global.InitExpr.Inst.Value.Float64 = Bits; + Global.InitExpr.Inst.Value.Float64 = 0; break; case wasm::WASM_TYPE_EXTERNREF: Global.InitExpr.Inst.Opcode = wasm::WASM_OPCODE_REF_NULL; break; default: - llvm_unreachable("unexpected type"); + // Keep the entry: relocations and the symbol table look up this + // global's index in WasmIndices. The error discards the object. + reportUnsupportedGlobal(WS, Global.Type.Type); + Global.Type.Type = uint8_t(wasm::ValType::I32); + Global.InitExpr.Inst.Opcode = wasm::WASM_OPCODE_I32_CONST; + Global.InitExpr.Inst.Value.Int32 = 0; + break; + } + if (WS.hasGlobalInitExpr()) { + assert(WS.getGlobalInitExpr().Inst.Opcode == + Global.InitExpr.Inst.Opcode && + "global init expression does not match the global's type"); + Global.InitExpr = WS.getGlobalInitExpr(); } assert(!WasmIndices.contains(&WS)); WasmIndices[&WS] = Global.Index; diff --git a/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp b/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp index 475c73c6f81b2..ff4cf4c4169d1 100644 --- a/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp +++ b/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp @@ -19,6 +19,8 @@ #include "MCTargetDesc/WebAssemblyMCTypeUtilities.h" #include "MCTargetDesc/WebAssemblyTargetStreamer.h" #include "TargetInfo/WebAssemblyTargetInfo.h" +#include "llvm/ADT/APFloat.h" +#include "llvm/ADT/MapVector.h" #include "llvm/MC/MCContext.h" #include "llvm/MC/MCExpr.h" #include "llvm/MC/MCInst.h" @@ -237,6 +239,26 @@ static wasm::WasmLimits defaultLimits() { return {wasm::WASM_LIMITS_FLAG_NONE, 0, 0, 0}; } +// Compare only the union member the opcode selects: bytes past it are +// uninitialized. +static bool sameInitExpr(const wasm::WasmInitExprMVP &A, + const wasm::WasmInitExprMVP &B) { + if (A.Opcode != B.Opcode) + return false; + switch (A.Opcode) { + case wasm::WASM_OPCODE_I32_CONST: + return A.Value.Int32 == B.Value.Int32; + case wasm::WASM_OPCODE_I64_CONST: + return A.Value.Int64 == B.Value.Int64; + case wasm::WASM_OPCODE_F32_CONST: + return A.Value.Float32 == B.Value.Float32; + case wasm::WASM_OPCODE_F64_CONST: + return A.Value.Float64 == B.Value.Float64; + default: + llvm_unreachable("only number types carry an init expression"); + } +} + static MCSymbolWasm *getOrCreateFunctionTableSymbol(MCContext &Ctx, const StringRef &Name, bool Is64) { @@ -292,6 +314,7 @@ class WebAssemblyAsmParser final : public MCTargetAsmParser { MCSymbolWasm *DefaultFunctionTable = nullptr; MCSymbol *LastFunctionLabel = nullptr; + MapVector<MCSymbolWasm *, SMLoc> GlobalInitLocs; bool Is64; @@ -470,25 +493,95 @@ class WebAssemblyAsmParser final : public MCTargetAsmParser { Parser.Lex(); } - bool parseSingleFloat(bool IsNegative, OperandVector &Operands) { + // Parses a global's initial value, spelled like the matching const + // instruction's immediate: `i32.const 42`, `f32.const 0x1p1`. + bool parseGlobalInit(wasm::ValType Type, + std::optional<wasm::WasmInitExpr> &Result) { + auto MnemonicTok = Lexer.getTok(); + auto Mnemonic = expectIdent(); + if (Mnemonic.empty()) + return true; + StringRef TypeName = Mnemonic; + std::optional<wasm::ValType> InitType; + if (TypeName.consume_back(".const")) + InitType = WebAssembly::parseType(TypeName); + if (!InitType) + return error("Expected a const instruction in .globalinit: ", + MnemonicTok); + if (*InitType != Type) + return error("Initial value does not match the global's type: ", + MnemonicTok); + if (!WebAssembly::isNumType(Type)) + return error("Initial value is not supported for this global type: ", + MnemonicTok); + + SMLoc SignLoc = Lexer.getTok().getLoc(); + bool Negative = isNext(AsmToken::Minus); + auto ValueTok = Lexer.getTok(); + uint64_t Bits; + if (Type == wasm::ValType::I32 || Type == wasm::ValType::I64) { + if (ValueTok.isNot(AsmToken::Integer) && ValueTok.isNot(AsmToken::BigNum)) + return error("Expected integer constant, instead got: ", ValueTok); + // Accept either the signed or the unsigned spelling of a value of this + // width, the way the fixed-width integer directives do. + APInt Val = ValueTok.getAPIntVal(); + Val = Val.zext(Val.getBitWidth() + 1); + if (Negative) + Val.negate(); + unsigned Width = Type == wasm::ValType::I32 ? 32 : 64; + if (!Val.isIntN(Width) && !Val.isSignedIntN(Width)) + return error("Integer constant does not fit in the global's type: " + + Twine(Negative ? "-" : "") + ValueTok.getString(), + Negative ? SignLoc : ValueTok.getLoc()); + Parser.Lex(); + Bits = Val.trunc(64).getZExtValue(); + } else { + double Val; + if (ValueTok.is(AsmToken::Real)) { + if (parseSingleFloat(Negative, Val)) + return true; + } else if (parseSpecialFloatMaybe(Negative, Val)) { + return error("Expected floating-point constant, instead got: ", + ValueTok); + } + // A host float conversion need not keep a NaN's sign. + APFloat Flt(Val); + if (Type == wasm::ValType::F32) { + bool LosesInfo; + Flt.convert(APFloat::IEEEsingle(), APFloat::rmNearestTiesToEven, + &LosesInfo); + } + Bits = Flt.bitcastToAPInt().getZExtValue(); + } + Result = WebAssembly::makeGlobalInitExpr(Type, Bits); + assert(Result && "a parsed const type must have an init expression"); + return false; + } + + bool parseSingleFloat(bool IsNegative, double &Val) { auto &Flt = Lexer.getTok(); - double Val; if (Flt.getString().getAsDouble(Val, false)) return error("Cannot parse real: ", Flt); if (IsNegative) Val = -Val; - Operands.push_back(std::make_unique<WebAssemblyOperand>( - Flt.getLoc(), Flt.getEndLoc(), WebAssemblyOperand::FltOp{Val})); Parser.Lex(); return false; } - bool parseSpecialFloatMaybe(bool IsNegative, OperandVector &Operands) { + bool parseSingleFloat(bool IsNegative, OperandVector &Operands) { + SMLoc Start = Lexer.getTok().getLoc(), End = Lexer.getTok().getEndLoc(); + double Val; + if (parseSingleFloat(IsNegative, Val)) + return true; + Operands.push_back(std::make_unique<WebAssemblyOperand>( + Start, End, WebAssemblyOperand::FltOp{Val})); + return false; + } + + bool parseSpecialFloatMaybe(bool IsNegative, double &Val) { if (Lexer.isNot(AsmToken::Identifier)) return true; - auto &Flt = Lexer.getTok(); - auto S = Flt.getString(); - double Val; + auto S = Lexer.getTok().getString(); if (S.compare_insensitive("infinity") == 0) { Val = std::numeric_limits<double>::infinity(); } else if (S.compare_insensitive("nan") == 0) { @@ -498,12 +591,20 @@ class WebAssemblyAsmParser final : public MCTargetAsmParser { } if (IsNegative) Val = -Val; - Operands.push_back(std::make_unique<WebAssemblyOperand>( - Flt.getLoc(), Flt.getEndLoc(), WebAssemblyOperand::FltOp{Val})); Parser.Lex(); return false; } + bool parseSpecialFloatMaybe(bool IsNegative, OperandVector &Operands) { + SMLoc Start = Lexer.getTok().getLoc(), End = Lexer.getTok().getEndLoc(); + double Val; + if (parseSpecialFloatMaybe(IsNegative, Val)) + return true; + Operands.push_back(std::make_unique<WebAssemblyOperand>( + Start, End, WebAssemblyOperand::FltOp{Val})); + return false; + } + bool addMemOrderOrDefault(OperandVector &Operands) { auto &Tok = Lexer.getTok(); int64_t Order = wasm::WASM_MEM_ORDER_SEQ_CST; @@ -1004,6 +1105,7 @@ class WebAssemblyAsmParser final : public MCTargetAsmParser { auto &Ctx = Out.getContext(); if (DirectiveID.getString() == ".globaltype") { + auto SymNameTok = Lexer.getTok(); auto SymName = expectIdent(); if (SymName.empty()) return ParseStatus::Failure; @@ -1034,6 +1136,15 @@ class WebAssemblyAsmParser final : public MCTargetAsmParser { // Now set this symbol with the correct type. auto *WasmSym = static_cast<MCSymbolWasm *>(Ctx.getOrCreateSymbol(SymName)); + if (WasmSym->hasGlobalInitExpr()) { + std::optional<wasm::WasmInitExpr> Expected = + WebAssembly::makeGlobalInitExpr(*Type, 0); + if (!Expected || + Expected->Inst.Opcode != WasmSym->getGlobalInitExpr().Inst.Opcode) + return error( + "Type does not match the .globalinit already given for: ", + SymNameTok); + } WasmSym->setType(wasm::WASM_SYMBOL_TYPE_GLOBAL); WasmSym->setGlobalType(wasm::WasmGlobalType{uint8_t(*Type), Mutable}); // And emit the directive again. @@ -1041,6 +1152,36 @@ class WebAssemblyAsmParser final : public MCTargetAsmParser { return expect(AsmToken::EndOfStatement, "EOL"); } + if (DirectiveID.getString() == ".globalinit") { + // .globalinit SYM, <type>.const VALUE + auto SymNameTok = Lexer.getTok(); + auto SymName = expectIdent(); + if (SymName.empty()) + return ParseStatus::Failure; + if (expect(AsmToken::Comma, ",")) + return ParseStatus::Failure; + auto *WasmSym = + static_cast<MCSymbolWasm *>(Ctx.getOrCreateSymbol(SymName)); + if (WasmSym->getType() && !WasmSym->isGlobal()) + return error(".globalinit is only valid for a global: ", SymNameTok); + if (!WasmSym->isGlobal() || !WasmSym->hasGlobalType()) + return error(".globalinit requires a preceding .globaltype for: ", + SymNameTok); + std::optional<wasm::WasmInitExpr> InitExpr; + if (parseGlobalInit( + static_cast<wasm::ValType>(WasmSym->getGlobalType().Type), + InitExpr)) + return ParseStatus::Failure; + // An .include'd header may repeat the same .globalinit. + if (WasmSym->hasGlobalInitExpr() && + !sameInitExpr(WasmSym->getGlobalInitExpr().Inst, InitExpr->Inst)) + return error("Conflicting .globalinit for: ", SymNameTok); + WasmSym->setGlobalInitExpr(*InitExpr); + GlobalInitLocs.try_emplace(WasmSym, SymNameTok.getLoc()); + TOut.emitGlobalInit(WasmSym); + return expect(AsmToken::EndOfStatement, "EOL"); + } + if (DirectiveID.getString() == ".tabletype") { // .tabletype SYM, ELEMTYPE[, MINSIZE[, MAXSIZE]] auto SymName = expectIdent(); @@ -1384,7 +1525,14 @@ class WebAssemblyAsmParser final : public MCTargetAsmParser { TC.clear(); } - void onEndOfFile() override { ensureEmptyNestingStack(); } + void onEndOfFile() override { + ensureEmptyNestingStack(); + // A later directive can re-declare the symbol as another kind, including + // .type, which this parser does not handle. + for (auto &[Sym, Loc] : GlobalInitLocs) + if (!Sym->isGlobal()) + error(".globalinit is only valid for a global: " + Sym->getName(), Loc); + } }; } // end anonymous namespace diff --git a/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyInstPrinter.cpp b/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyInstPrinter.cpp index 5bfaffba3e578..5a74d13641c02 100644 --- a/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyInstPrinter.cpp +++ b/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyInstPrinter.cpp @@ -302,30 +302,6 @@ void WebAssemblyInstPrinter::printInst(const MCInst *MI, uint64_t Address, } } -static std::string toString(const APFloat &FP) { - // Print NaNs with custom payloads specially. - if (FP.isNaN() && !FP.bitwiseIsEqual(APFloat::getQNaN(FP.getSemantics())) && - !FP.bitwiseIsEqual( - APFloat::getQNaN(FP.getSemantics(), /*Negative=*/true))) { - APInt AI = FP.bitcastToAPInt(); - return std::string(AI.isNegative() ? "-" : "") + "nan:0x" + - utohexstr(AI.getZExtValue() & - (AI.getBitWidth() == 32 ? INT64_C(0x007fffff) - : INT64_C(0x000fffffffffffff)), - /*LowerCase=*/true); - } - - // Use C99's hexadecimal floating-point representation. - static const size_t BufBytes = 128; - char Buf[BufBytes]; - auto Written = FP.convertToHexString( - Buf, /*HexDigits=*/0, /*UpperCase=*/false, APFloat::rmNearestTiesToEven); - (void)Written; - assert(Written != 0); - assert(Written < BufBytes); - return Buf; -} - void WebAssemblyInstPrinter::printOperand(const MCInst *MI, unsigned OpNo, const MCSubtargetInfo &STI, raw_ostream &O, bool IsVariadicDef) { @@ -347,9 +323,11 @@ void WebAssemblyInstPrinter::printOperand(const MCInst *MI, unsigned OpNo, } else if (Op.isImm()) { O << Op.getImm(); } else if (Op.isSFPImm()) { - O << ::toString(APFloat(APFloat::IEEEsingle(), APInt(32, Op.getSFPImm()))); + O << WebAssembly::floatToString( + APFloat(APFloat::IEEEsingle(), APInt(32, Op.getSFPImm()))); } else if (Op.isDFPImm()) { - O << ::toString(APFloat(APFloat::IEEEdouble(), APInt(64, Op.getDFPImm()))); + O << WebAssembly::floatToString( + APFloat(APFloat::IEEEdouble(), APInt(64, Op.getDFPImm()))); } else { assert(Op.isExpr() && "unknown operand kind in printOperand"); // call_indirect instructions have a TYPEINDEX operand that we print diff --git a/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyMCTypeUtilities.cpp b/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyMCTypeUtilities.cpp index 6e99fc3b9b9fc..d187f8a9e7651 100644 --- a/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyMCTypeUtilities.cpp +++ b/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyMCTypeUtilities.cpp @@ -13,6 +13,9 @@ #include "WebAssemblyMCTypeUtilities.h" #include "WebAssemblyMCTargetDesc.h" +#include "llvm/ADT/APFloat.h" +#include "llvm/ADT/APInt.h" +#include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringSwitch.h" using namespace llvm; @@ -120,3 +123,53 @@ wasm::ValType WebAssembly::regClassToValType(unsigned RC) { llvm_unreachable("unexpected type"); } } + +std::optional<wasm::WasmInitExpr> +WebAssembly::makeGlobalInitExpr(wasm::ValType Type, uint64_t Bits) { + wasm::WasmInitExpr Expr; + Expr.Extended = false; + switch (Type) { + case wasm::ValType::I32: + Expr.Inst.Opcode = wasm::WASM_OPCODE_I32_CONST; + Expr.Inst.Value.Int32 = static_cast<int32_t>(static_cast<uint32_t>(Bits)); + return Expr; + case wasm::ValType::I64: + Expr.Inst.Opcode = wasm::WASM_OPCODE_I64_CONST; + Expr.Inst.Value.Int64 = static_cast<int64_t>(Bits); + return Expr; + case wasm::ValType::F32: + Expr.Inst.Opcode = wasm::WASM_OPCODE_F32_CONST; + Expr.Inst.Value.Float32 = static_cast<uint32_t>(Bits); + return Expr; + case wasm::ValType::F64: + Expr.Inst.Opcode = wasm::WASM_OPCODE_F64_CONST; + Expr.Inst.Value.Float64 = Bits; + return Expr; + default: + return std::nullopt; + } +} + +std::string WebAssembly::floatToString(const APFloat &FP) { + // Print NaNs with custom payloads specially. + if (FP.isNaN() && !FP.bitwiseIsEqual(APFloat::getQNaN(FP.getSemantics())) && + !FP.bitwiseIsEqual( + APFloat::getQNaN(FP.getSemantics(), /*Negative=*/true))) { + APInt AI = FP.bitcastToAPInt(); + return std::string(AI.isNegative() ? "-" : "") + "nan:0x" + + utohexstr(AI.getZExtValue() & + (AI.getBitWidth() == 32 ? INT64_C(0x007fffff) + : INT64_C(0x000fffffffffffff)), + /*LowerCase=*/true); + } + + // Use C99's hexadecimal floating-point representation. + static const size_t BufBytes = 128; + char Buf[BufBytes]; + auto Written = FP.convertToHexString( + Buf, /*HexDigits=*/0, /*UpperCase=*/false, APFloat::rmNearestTiesToEven); + (void)Written; + assert(Written != 0); + assert(Written < BufBytes); + return Buf; +} diff --git a/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyMCTypeUtilities.h b/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyMCTypeUtilities.h index aa630c7ddd6ad..6ac811c182f46 100644 --- a/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyMCTypeUtilities.h +++ b/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyMCTypeUtilities.h @@ -19,6 +19,8 @@ namespace llvm { +class APFloat; + namespace WebAssembly { /// Used as immediate MachineOperands for block signatures @@ -50,6 +52,11 @@ inline bool isRefType(wasm::ValType Type) { Type == wasm::ValType::EXNREF; } +inline bool isNumType(wasm::ValType Type) { + return Type == wasm::ValType::I32 || Type == wasm::ValType::I64 || + Type == wasm::ValType::F32 || Type == wasm::ValType::F64; +} + // Convert ValType or a list/signature of ValTypes to a string. // Convert an unsigned integer, which can be among wasm::ValType enum, to its @@ -73,6 +80,15 @@ wasm::ValType regClassToValType(unsigned RC); std::optional<wasm::ValType> parseType(StringRef Type); BlockType parseBlockType(StringRef Type); +// Build the `<type>.const` init expression that gives a global of type Type the +// value whose raw bits are Bits, or nullopt if Type is not a number type. +std::optional<wasm::WasmInitExpr> makeGlobalInitExpr(wasm::ValType Type, + uint64_t Bits); + +// Spell a float the way the WebAssembly text format does: a C99 hexadecimal +// floating-point literal, or nan:0x<payload> for a non-canonical NaN. +std::string floatToString(const APFloat &FP); + } // end namespace WebAssembly } // end namespace llvm diff --git a/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyTargetStreamer.cpp b/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyTargetStreamer.cpp index dba974d60e5bc..6eeeba715cf61 100644 --- a/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyTargetStreamer.cpp +++ b/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyTargetStreamer.cpp @@ -15,6 +15,8 @@ #include "MCTargetDesc/WebAssemblyTargetStreamer.h" #include "MCTargetDesc/WebAssemblyMCAsmInfo.h" #include "MCTargetDesc/WebAssemblyMCTypeUtilities.h" +#include "llvm/ADT/APFloat.h" +#include "llvm/ADT/APInt.h" #include "llvm/MC/MCContext.h" #include "llvm/MC/MCSectionWasm.h" #include "llvm/MC/MCSymbolWasm.h" @@ -73,6 +75,35 @@ void WebAssemblyTargetAsmStreamer::emitGlobalType(const MCSymbolWasm *Sym) { OS << '\n'; } +void WebAssemblyTargetAsmStreamer::emitGlobalInit(const MCSymbolWasm *Sym) { + assert(Sym->isGlobal()); + if (!Sym->hasGlobalInitExpr()) + return; + const wasm::WasmInitExprMVP &Inst = Sym->getGlobalInitExpr().Inst; + OS << "\t.globalinit\t" << Sym->getName() << ", "; + switch (Inst.Opcode) { + case wasm::WASM_OPCODE_I32_CONST: + OS << "i32.const " << Inst.Value.Int32; + break; + case wasm::WASM_OPCODE_I64_CONST: + OS << "i64.const " << Inst.Value.Int64; + break; + case wasm::WASM_OPCODE_F32_CONST: + OS << "f32.const " + << WebAssembly::floatToString( + APFloat(APFloat::IEEEsingle(), APInt(32, Inst.Value.Float32))); + break; + case wasm::WASM_OPCODE_F64_CONST: + OS << "f64.const " + << WebAssembly::floatToString( + APFloat(APFloat::IEEEdouble(), APInt(64, Inst.Value.Float64))); + break; + default: + llvm_unreachable("only number types carry an init expression"); + } + OS << '\n'; +} + void WebAssemblyTargetAsmStreamer::emitTableType(const MCSymbolWasm *Sym) { assert(Sym->isTable()); const wasm::WasmTableType &Type = Sym->getTableType(); diff --git a/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyTargetStreamer.h b/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyTargetStreamer.h index d1eb6f67176f2..da2f7e100738d 100644 --- a/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyTargetStreamer.h +++ b/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyTargetStreamer.h @@ -38,6 +38,8 @@ class WebAssemblyTargetStreamer : public MCTargetStreamer { virtual void emitIndIdx(const MCExpr *Value) = 0; /// .globaltype virtual void emitGlobalType(const MCSymbolWasm *Sym) = 0; + /// .globalinit + virtual void emitGlobalInit(const MCSymbolWasm *Sym) = 0; /// .tabletype virtual void emitTableType(const MCSymbolWasm *Sym) = 0; /// .tagtype @@ -67,6 +69,7 @@ class WebAssemblyTargetAsmStreamer final : public WebAssemblyTargetStreamer { void emitFunctionType(const MCSymbolWasm *Sym) override; void emitIndIdx(const MCExpr *Value) override; void emitGlobalType(const MCSymbolWasm *Sym) override; + void emitGlobalInit(const MCSymbolWasm *Sym) override; void emitTableType(const MCSymbolWasm *Sym) override; void emitTagType(const MCSymbolWasm *Sym) override; void emitImportModule(const MCSymbolWasm *Sym, StringRef ImportModule) override; @@ -83,6 +86,7 @@ class WebAssemblyTargetWasmStreamer final : public WebAssemblyTargetStreamer { void emitFunctionType(const MCSymbolWasm *Sym) override {} void emitIndIdx(const MCExpr *Value) override; void emitGlobalType(const MCSymbolWasm *Sym) override {} + void emitGlobalInit(const MCSymbolWasm *Sym) override {} void emitTableType(const MCSymbolWasm *Sym) override {} void emitTagType(const MCSymbolWasm *Sym) override {} void emitImportModule(const MCSymbolWasm *Sym, @@ -103,6 +107,7 @@ class WebAssemblyTargetNullStreamer final : public WebAssemblyTargetStreamer { void emitFunctionType(const MCSymbolWasm *) override {} void emitIndIdx(const MCExpr *) override {} void emitGlobalType(const MCSymbolWasm *) override {} + void emitGlobalInit(const MCSymbolWasm *) override {} void emitTableType(const MCSymbolWasm *) override {} void emitTagType(const MCSymbolWasm *) override {} void emitImportModule(const MCSymbolWasm *, StringRef) override {} diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp index 26e224a597540..4c1c610ec5398 100644 --- a/llvm/lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp +++ b/llvm/lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp @@ -190,24 +190,41 @@ MCSymbolWasm *WebAssemblyAsmPrinter::getMCSymbolForFunction( return WasmSym; } -static std::optional<uint64_t> getWasmGlobalInitBits(const Constant *C) { - // Sub-i32 ints and half/bfloat legalize to a single i32/f32 MVT and so pass - // wasmSymbolSetType(), but their bits are not an i32/f32 init value; gate on - // the exact IR type so they fall back to the default 0 rather than emit a - // garbage init expr. - if (const auto *CI = dyn_cast<ConstantInt>(C)) { - unsigned BW = CI->getType()->getIntegerBitWidth(); - if (BW == 32 || BW == 64) - return CI->getValue().getZExtValue(); - return std::nullopt; +static std::optional<wasm::WasmInitExpr> +getWasmGlobalInitExpr(const Constant *C, wasm::ValType Type) { + // A single-element aggregate legalizes to its element's type. + while (C->getType()->isAggregateType() || C->getType()->isVectorTy()) { + if (C->getAggregateElement(1u)) + return std::nullopt; + C = C->getAggregateElement(0u); + if (!C) + return std::nullopt; + } + switch (Type) { + case wasm::ValType::I32: + case wasm::ValType::I64: { + // i1, i8, i16 and half also legalize to i32, but loads and stores of such + // a global do not use global.get or global.set. + const auto *CI = dyn_cast<ConstantInt>(C); + unsigned Width = Type == wasm::ValType::I32 ? 32 : 64; + if (!CI || CI->getBitWidth() != Width) + return std::nullopt; + return WebAssembly::makeGlobalInitExpr(Type, CI->getValue().getZExtValue()); + } + case wasm::ValType::F32: + case wasm::ValType::F64: { + // bfloat also legalizes to f32, but loads and stores of such a global + // fail instruction selection. + const auto *CF = dyn_cast<ConstantFP>(C); + if (!CF || (Type == wasm::ValType::F32 ? !CF->getType()->isFloatTy() + : !CF->getType()->isDoubleTy())) + return std::nullopt; + return WebAssembly::makeGlobalInitExpr( + Type, CF->getValueAPF().bitcastToAPInt().getZExtValue()); } - if (const auto *CF = dyn_cast<ConstantFP>(C)) { - Type *T = CF->getType(); - if (T->isFloatTy() || T->isDoubleTy()) - return CF->getValueAPF().bitcastToAPInt().getZExtValue(); + default: return std::nullopt; } - return std::nullopt; } void WebAssemblyAsmPrinter::emitGlobalVariable(const GlobalVariable *GV) { @@ -258,12 +275,24 @@ void WebAssemblyAsmPrinter::emitGlobalVariable(const GlobalVariable *GV) { emitVisibility(Sym, GV->getVisibility(), !GV->isDeclaration()); - // WasmObjectWriter reads these init bits back off the symbol to emit the init - // expression instead of the type's default 0. if (GV->hasInitializer() && Sym->isGlobal()) { - if (std::optional<uint64_t> Bits = - getWasmGlobalInitBits(GV->getInitializer())) - Sym->setGlobalInitValue(*Bits); + const Constant *Init = GV->getInitializer(); + auto Type = static_cast<wasm::ValType>(Sym->getGlobalType().Type); + // The object writer gives reference types ref.null and rejects v128. + // undef, poison and zero take the type's default. + if (WebAssembly::isNumType(Type) && !isa<UndefValue>(Init) && + !Init->isNullValue()) { + if (std::optional<wasm::WasmInitExpr> InitExpr = + getWasmGlobalInitExpr(Init, Type)) { + Sym->setGlobalInitExpr(*InitExpr); + } else { + // Keep emitting the declaration so the printed assembly stays well + // formed. The error discards the object. + OutContext.reportError(SMLoc(), "unsupported initializer for " + "WebAssembly global '" + + Sym->getName() + "'"); + } + } } emitSymbolType(Sym); @@ -366,6 +395,7 @@ void WebAssemblyAsmPrinter::emitSymbolType(const MCSymbolWasm *Sym) { switch (*WasmTy) { case wasm::WASM_SYMBOL_TYPE_GLOBAL: getTargetStreamer()->emitGlobalType(Sym); + getTargetStreamer()->emitGlobalInit(Sym); break; case wasm::WASM_SYMBOL_TYPE_TAG: getTargetStreamer()->emitTagType(Sym); diff --git a/llvm/test/CodeGen/WebAssembly/global-addrspace.ll b/llvm/test/CodeGen/WebAssembly/global-addrspace.ll index d6f0439642a68..9c2bd7677c5a7 100644 --- a/llvm/test/CodeGen/WebAssembly/global-addrspace.ll +++ b/llvm/test/CodeGen/WebAssembly/global-addrspace.ll @@ -5,26 +5,31 @@ ;; (e.g., in modules without functions) and correctly emit their initializers. ; CHECK: .globaltype wasm_var, i32 +; CHECK-NEXT: .globalinit wasm_var, i32.const 42 ; CHECK: .globl wasm_var ; CHECK-LABEL: wasm_var: @wasm_var = addrspace(1) global i32 42 ; CHECK: .globaltype wasm_var_float, f32 +; CHECK-NEXT: .globalinit wasm_var_float, f32.const 0x1.91eb86p1 ; CHECK: .globl wasm_var_float ; CHECK-LABEL: wasm_var_float: @wasm_var_float = addrspace(1) global float 0x40091EB860000000 ; CHECK: .globaltype wasm_var_i64, i64 +; CHECK-NEXT: .globalinit wasm_var_i64, i64.const 1234567890 ; CHECK: .globl wasm_var_i64 ; CHECK-LABEL: wasm_var_i64: @wasm_var_i64 = addrspace(1) global i64 1234567890 ; CHECK: .globaltype wasm_var_f64, f64 +; CHECK-NEXT: .globalinit wasm_var_f64, f64.const -0x0p0 ; CHECK: .globl wasm_var_f64 ; CHECK-LABEL: wasm_var_f64: @wasm_var_f64 = local_unnamed_addr addrspace(1) global double -0.0 ; CHECK: .globaltype wasm_external, i32 ; CHECK-NOT: .global wasm_external +; CHECK-NOT: .globalinit wasm_external ; CHECK-NOT: wasm_external: @wasm_external = external addrspace(1) global i32 diff --git a/llvm/test/CodeGen/WebAssembly/global-init-invalid.ll b/llvm/test/CodeGen/WebAssembly/global-init-invalid.ll new file mode 100644 index 0000000000000..140fb80b84144 --- /dev/null +++ b/llvm/test/CodeGen/WebAssembly/global-init-invalid.ll @@ -0,0 +1,35 @@ +; RUN: not llc < %s -mtriple=wasm32-unknown-unknown -filetype=obj -o /dev/null 2>&1 | FileCheck %s +; RUN: not llc < %s -mtriple=wasm32-unknown-unknown -filetype=asm -o - 2>&1 | FileCheck %s --check-prefix=ASM + +; Initializers that a Wasm init expression cannot represent are diagnosed. + +; The declaration is still printed after the diagnostic. +; ASM: error: unsupported initializer for WebAssembly global 'narrow_int' +; ASM: .globl narrow_int +; ASM: narrow_int: + +; CHECK: error: unsupported initializer for WebAssembly global 'narrow_int' +@narrow_int = addrspace(1) global i8 5 + +; CHECK: error: unsupported initializer for WebAssembly global 'narrow_bool' +@narrow_bool = addrspace(1) global i1 true + +; CHECK: error: unsupported initializer for WebAssembly global 'short_int' +@short_int = addrspace(1) global i16 4660 + +; CHECK: error: unsupported initializer for WebAssembly global 'half_fp' +@half_fp = addrspace(1) global half 2.0 + +; CHECK: error: unsupported initializer for WebAssembly global 'bfloat_fp' +@bfloat_fp = addrspace(1) global bfloat 2.0 + +@data = global i32 7 +; CHECK: error: unsupported initializer for WebAssembly global 'sym_addr' +@sym_addr = addrspace(1) global ptr @data + +; CHECK: error: unsupported initializer for WebAssembly global 'sym_int' +@sym_int = addrspace(1) global i32 ptrtoint (ptr @data to i32) + +@arr = global [4 x i32] zeroinitializer +; CHECK: error: unsupported initializer for WebAssembly global 'sym_offset' +@sym_offset = addrspace(1) global ptr getelementptr inbounds ([4 x i32], ptr @arr, i32 0, i32 2) diff --git a/llvm/test/CodeGen/WebAssembly/global-init-reftype.ll b/llvm/test/CodeGen/WebAssembly/global-init-reftype.ll new file mode 100644 index 0000000000000..a9505cb2767d6 --- /dev/null +++ b/llvm/test/CodeGen/WebAssembly/global-init-reftype.ll @@ -0,0 +1,22 @@ +; RUN: llc < %s -mtriple=wasm32-unknown-unknown -mattr=+reference-types -filetype=obj | obj2yaml | FileCheck %s --implicit-check-not=error: + +; undef and zeroinitializer both give an externref global ref.null, with no +; diagnostic. + +@undefref = addrspace(1) global target("wasm.externref") undef +@nullref = addrspace(1) global target("wasm.externref") zeroinitializer + +; CHECK: - Type: GLOBAL +; CHECK-NEXT: Globals: +; CHECK-NEXT: - Index: 0 +; CHECK-NEXT: Type: EXTERNREF +; CHECK-NEXT: Mutable: true +; CHECK-NEXT: InitExpr: +; CHECK-NEXT: Opcode: REF_NULL +; CHECK-NEXT: Type: EXTERNREF +; CHECK-NEXT: - Index: 1 +; CHECK-NEXT: Type: EXTERNREF +; CHECK-NEXT: Mutable: true +; CHECK-NEXT: InitExpr: +; CHECK-NEXT: Opcode: REF_NULL +; CHECK-NEXT: Type: EXTERNREF diff --git a/llvm/test/CodeGen/WebAssembly/global-init.ll b/llvm/test/CodeGen/WebAssembly/global-init.ll index 72e5c180c649c..a0da7ff68e4d4 100644 --- a/llvm/test/CodeGen/WebAssembly/global-init.ll +++ b/llvm/test/CodeGen/WebAssembly/global-init.ll @@ -1,8 +1,10 @@ -; RUN: llc < %s -mtriple=wasm32-unknown-unknown -filetype=obj | obj2yaml | FileCheck %s +; RUN: llc < %s -mtriple=wasm32-unknown-unknown -filetype=obj | obj2yaml | FileCheck %s --check-prefix=OBJ --implicit-check-not=error: +; RUN: llc < %s -mtriple=wasm32-unknown-unknown | llvm-mc -triple=wasm32-unknown-unknown -filetype=obj | obj2yaml | FileCheck %s --check-prefix=OBJ +; RUN: llc < %s -mtriple=wasm32-unknown-unknown | FileCheck %s --check-prefix=ASM -; No function is defined here, so the AsmPrinter's Subtarget is null: emitting -; these address-space-1 globals must not crash, and each initializer must reach -; the Global section instead of the type's default 0. +; No function is defined here, so the AsmPrinter has no Subtarget. Emitting +; these globals must not crash, and each initializer must reach the Global +; section. @i32v = addrspace(1) global i32 33554435 @i64v = addrspace(1) global i64 8589934595 @@ -10,35 +12,124 @@ @f64v = addrspace(1) global double 2.0 @constv = addrspace(1) constant i32 42 -; CHECK: - Type: GLOBAL -; CHECK-NEXT: Globals: -; CHECK-NEXT: - Index: 0 -; CHECK-NEXT: Type: I32 -; CHECK-NEXT: Mutable: true -; CHECK-NEXT: InitExpr: -; CHECK-NEXT: Opcode: I32_CONST -; CHECK-NEXT: Value: 33554435 -; CHECK-NEXT: - Index: 1 -; CHECK-NEXT: Type: I64 -; CHECK-NEXT: Mutable: true -; CHECK-NEXT: InitExpr: -; CHECK-NEXT: Opcode: I64_CONST -; CHECK-NEXT: Value: 8589934595 -; CHECK-NEXT: - Index: 2 -; CHECK-NEXT: Type: F32 -; CHECK-NEXT: Mutable: true -; CHECK-NEXT: InitExpr: -; CHECK-NEXT: Opcode: F32_CONST -; CHECK-NEXT: Value: 1073741824 -; CHECK-NEXT: - Index: 3 -; CHECK-NEXT: Type: F64 -; CHECK-NEXT: Mutable: true -; CHECK-NEXT: InitExpr: -; CHECK-NEXT: Opcode: F64_CONST -; CHECK-NEXT: Value: 4611686018427387904 -; CHECK-NEXT: - Index: 4 -; CHECK-NEXT: Type: I32 -; CHECK-NEXT: Mutable: false -; CHECK-NEXT: InitExpr: -; CHECK-NEXT: Opcode: I32_CONST -; CHECK-NEXT: Value: 42 +; undef, poison and zero keep the type's default and are not diagnosed. +@undefv = addrspace(1) global i32 undef +@poisonv = addrspace(1) global i32 poison +@nullv = addrspace(1) global ptr null +@zagg = addrspace(1) global {i32} zeroinitializer +@znarrow = addrspace(1) global i8 0 + +; A single-element aggregate holds its element's value. +@agg = addrspace(1) global {i32} {i32 5} +@arr = addrspace(1) global [1 x i64] [i64 6] +@vec = addrspace(1) global <1 x float> <float 2.0> +@nest = addrspace(1) global {[1 x double]} {[1 x double] [double 2.0]} + +; ASM: .globaltype i32v, i32 +; ASM-NEXT: .globalinit i32v, i32.const 33554435 +; ASM: .globaltype i64v, i64 +; ASM-NEXT: .globalinit i64v, i64.const 8589934595 +; ASM: .globaltype f32v, f32 +; ASM-NEXT: .globalinit f32v, f32.const 0x1p1 +; ASM: .globaltype f64v, f64 +; ASM-NEXT: .globalinit f64v, f64.const 0x1p1 +; ASM: .globaltype constv, i32, immutable +; ASM-NEXT: .globalinit constv, i32.const 42 +; A default value needs no .globalinit line. +; ASM: .globaltype undefv, i32 +; ASM-NOT: .globalinit undefv +; ASM: .globaltype agg, i32 +; ASM-NEXT: .globalinit agg, i32.const 5 +; ASM: .globaltype arr, i64 +; ASM-NEXT: .globalinit arr, i64.const 6 +; ASM: .globaltype vec, f32 +; ASM-NEXT: .globalinit vec, f32.const 0x1p1 +; ASM: .globaltype nest, f64 +; ASM-NEXT: .globalinit nest, f64.const 0x1p1 + +; OBJ: - Type: GLOBAL +; OBJ-NEXT: Globals: +; OBJ-NEXT: - Index: 0 +; OBJ-NEXT: Type: I32 +; OBJ-NEXT: Mutable: true +; OBJ-NEXT: InitExpr: +; OBJ-NEXT: Opcode: I32_CONST +; OBJ-NEXT: Value: 33554435 +; OBJ-NEXT: - Index: 1 +; OBJ-NEXT: Type: I64 +; OBJ-NEXT: Mutable: true +; OBJ-NEXT: InitExpr: +; OBJ-NEXT: Opcode: I64_CONST +; OBJ-NEXT: Value: 8589934595 +; OBJ-NEXT: - Index: 2 +; OBJ-NEXT: Type: F32 +; OBJ-NEXT: Mutable: true +; OBJ-NEXT: InitExpr: +; OBJ-NEXT: Opcode: F32_CONST +; OBJ-NEXT: Value: 1073741824 +; OBJ-NEXT: - Index: 3 +; OBJ-NEXT: Type: F64 +; OBJ-NEXT: Mutable: true +; OBJ-NEXT: InitExpr: +; OBJ-NEXT: Opcode: F64_CONST +; OBJ-NEXT: Value: 4611686018427387904 +; OBJ-NEXT: - Index: 4 +; OBJ-NEXT: Type: I32 +; OBJ-NEXT: Mutable: false +; OBJ-NEXT: InitExpr: +; OBJ-NEXT: Opcode: I32_CONST +; OBJ-NEXT: Value: 42 +; OBJ-NEXT: - Index: 5 +; OBJ-NEXT: Type: I32 +; OBJ-NEXT: Mutable: true +; OBJ-NEXT: InitExpr: +; OBJ-NEXT: Opcode: I32_CONST +; OBJ-NEXT: Value: 0 +; OBJ-NEXT: - Index: 6 +; OBJ-NEXT: Type: I32 +; OBJ-NEXT: Mutable: true +; OBJ-NEXT: InitExpr: +; OBJ-NEXT: Opcode: I32_CONST +; OBJ-NEXT: Value: 0 +; OBJ-NEXT: - Index: 7 +; OBJ-NEXT: Type: I32 +; OBJ-NEXT: Mutable: true +; OBJ-NEXT: InitExpr: +; OBJ-NEXT: Opcode: I32_CONST +; OBJ-NEXT: Value: 0 +; OBJ-NEXT: - Index: 8 +; OBJ-NEXT: Type: I32 +; OBJ-NEXT: Mutable: true +; OBJ-NEXT: InitExpr: +; OBJ-NEXT: Opcode: I32_CONST +; OBJ-NEXT: Value: 0 +; OBJ-NEXT: - Index: 9 +; OBJ-NEXT: Type: I32 +; OBJ-NEXT: Mutable: true +; OBJ-NEXT: InitExpr: +; OBJ-NEXT: Opcode: I32_CONST +; OBJ-NEXT: Value: 0 +; OBJ-NEXT: - Index: 10 +; OBJ-NEXT: Type: I32 +; OBJ-NEXT: Mutable: true +; OBJ-NEXT: InitExpr: +; OBJ-NEXT: Opcode: I32_CONST +; OBJ-NEXT: Value: 5 +; OBJ-NEXT: - Index: 11 +; OBJ-NEXT: Type: I64 +; OBJ-NEXT: Mutable: true +; OBJ-NEXT: InitExpr: +; OBJ-NEXT: Opcode: I64_CONST +; OBJ-NEXT: Value: 6 +; OBJ-NEXT: - Index: 12 +; OBJ-NEXT: Type: F32 +; OBJ-NEXT: Mutable: true +; OBJ-NEXT: InitExpr: +; OBJ-NEXT: Opcode: F32_CONST +; OBJ-NEXT: Value: 1073741824 +; OBJ-NEXT: - Index: 13 +; OBJ-NEXT: Type: F64 +; OBJ-NEXT: Mutable: true +; OBJ-NEXT: InitExpr: +; OBJ-NEXT: Opcode: F64_CONST +; OBJ-NEXT: Value: 4611686018427387904 diff --git a/llvm/test/CodeGen/WebAssembly/global-unsupported-type.ll b/llvm/test/CodeGen/WebAssembly/global-unsupported-type.ll new file mode 100644 index 0000000000000..29a16a2573c4f --- /dev/null +++ b/llvm/test/CodeGen/WebAssembly/global-unsupported-type.ll @@ -0,0 +1,12 @@ +; RUN: split-file %s %t +; RUN: not llc < %t/vec.ll -mtriple=wasm32-unknown-unknown -mattr=+simd128 -filetype=obj -o /dev/null 2>&1 | FileCheck %s --check-prefix=V128 +; RUN: not llc < %t/fref.ll -mtriple=wasm32-unknown-unknown -mattr=+reference-types -filetype=obj -o /dev/null 2>&1 | FileCheck %s --check-prefix=FUNCREF + +; V128: error: WebAssembly global 'vec' has a type that cannot be initialized +; FUNCREF: error: WebAssembly global 'fref' has a type that cannot be initialized + +;--- vec.ll +@vec = addrspace(1) global <4 x i32> zeroinitializer + +;--- fref.ll +@fref = addrspace(1) global target("wasm.funcref") zeroinitializer diff --git a/llvm/test/MC/WebAssembly/globalinit-invalid.s b/llvm/test/MC/WebAssembly/globalinit-invalid.s new file mode 100644 index 0000000000000..13377388da63e --- /dev/null +++ b/llvm/test/MC/WebAssembly/globalinit-invalid.s @@ -0,0 +1,77 @@ +# RUN: not llvm-mc -triple=wasm32 %s 2>&1 | FileCheck %s + +# CHECK: error: .globalinit requires a preceding .globaltype for: nodecl +.globalinit nodecl, i32.const 1 + +# CHECK: error: Expected identifier, got: 42 +.globalinit 42, i32.const 1 + +# A symbol of another kind cannot take an initial value. +.functype fn () -> () +# CHECK: error: .globalinit is only valid for a global: fn +.globalinit fn, i32.const 1 +.tabletype tbl, externref +# CHECK: error: .globalinit is only valid for a global: tbl +.globalinit tbl, i32.const 1 +.tagtype tag i32 +# CHECK: error: .globalinit is only valid for a global: tag +.globalinit tag, i32.const 1 + +.globaltype sym, i32 +# CHECK: error: Expected a const instruction in .globalinit: hello +.globalinit sym, hello +# CHECK: error: Initial value does not match the global's type: f32.const +.globalinit sym, f32.const 1.0 +# CHECK: error: Expected integer constant, instead got: +.globalinit sym, i32.const +# CHECK: error: Expected EOL, instead got: , +.globalinit sym, i32.const 42, + +# CHECK: error: Integer constant does not fit in the global's type: 4294967296 +.globalinit sym, i32.const 4294967296 +# CHECK: :[[@LINE+1]]:28: error: Integer constant does not fit in the global's type: -4294967295 +.globalinit sym, i32.const -4294967295 + +.globaltype i64sym, i64 +# CHECK: error: Integer constant does not fit in the global's type: 18446744073709551616 +.globalinit i64sym, i64.const 18446744073709551616 + +.globaltype vec, v128 +# CHECK: error: Initial value is not supported for this global type: v128.const +.globalinit vec, v128.const 0 + +.globaltype flt, f32 +# CHECK: error: Expected floating-point constant, instead got: hello +.globalinit flt, f32.const hello +# CHECK: error: Expected floating-point constant, instead got: notanumber +.globalinit flt, f32.const notanumber +# The value takes the spellings an f32.const operand takes. +# CHECK: error: Cannot parse real: 0.1 +.globalinit flt, f32.const 0.1 +# CHECK: error: Expected floating-point constant, instead got: inf +.globalinit flt, f32.const inf +# CHECK: error: Expected floating-point constant, instead got: 1 +.globalinit flt, f32.const 1 + +# A later .globaltype may repeat the type the value was given, but not +# contradict it. +.globaltype retyped, i32 +.globalinit retyped, i32.const 42 +.globaltype retyped, i32 +# CHECK: error: Type does not match the .globalinit already given for: retyped +.globaltype retyped, f64 +# CHECK: error: Type does not match the .globalinit already given for: retyped +.globaltype retyped, v128 + +# Repeating the same initial value is fine; contradicting it is not. +.globaltype conflict, i32 +.globalinit conflict, i32.const 1 +.globalinit conflict, i32.const 1 +# CHECK: error: Conflicting .globalinit for: conflict +.globalinit conflict, i32.const 2 + +# nan and -nan are different values. +.globaltype nanconflict, f32 +.globalinit nanconflict, f32.const nan +# CHECK: error: Conflicting .globalinit for: nanconflict +.globalinit nanconflict, f32.const -nan diff --git a/llvm/test/MC/WebAssembly/globalinit-not-global.s b/llvm/test/MC/WebAssembly/globalinit-not-global.s new file mode 100644 index 0000000000000..0fd2e85062869 --- /dev/null +++ b/llvm/test/MC/WebAssembly/globalinit-not-global.s @@ -0,0 +1,32 @@ +# RUN: not llvm-mc -triple=wasm32-unknown-unknown %s -o /dev/null 2>&1 | FileCheck %s + +# Re-declaring a global as another kind of symbol would drop its value. + + .globaltype fn, i32 +# CHECK: :[[@LINE+1]]:14: error: .globalinit is only valid for a global: fn + .globalinit fn, i32.const 1 + .functype fn () -> () + .globl fn +fn: + .functype fn () -> () + end_function + + .globaltype obj, i32 +# CHECK: :[[@LINE+1]]:14: error: .globalinit is only valid for a global: obj + .globalinit obj, i32.const 2 + .type obj,@object + .section .data.obj,"",@ + .globl obj +obj: + .int32 0 + .size obj, 4 + + .globaltype tbl, i32 +# CHECK: :[[@LINE+1]]:14: error: .globalinit is only valid for a global: tbl + .globalinit tbl, i32.const 3 + .tabletype tbl, externref + + .globaltype tag, i32 +# CHECK: :[[@LINE+1]]:14: error: .globalinit is only valid for a global: tag + .globalinit tag, i32.const 4 + .tagtype tag i32 diff --git a/llvm/test/MC/WebAssembly/globalinit-undefined.s b/llvm/test/MC/WebAssembly/globalinit-undefined.s new file mode 100644 index 0000000000000..5928192e6cd29 --- /dev/null +++ b/llvm/test/MC/WebAssembly/globalinit-undefined.s @@ -0,0 +1,22 @@ +# RUN: not llvm-mc -triple=wasm32-unknown-unknown -filetype=obj %s -o /dev/null 2>&1 | FileCheck %s +# RUN: llvm-mc -triple=wasm32-unknown-unknown %s | not llvm-mc -triple=wasm32-unknown-unknown -filetype=obj -o /dev/null 2>&1 | FileCheck %s + +# An undefined global is imported, so the value given here would be lost. + +# CHECK: error: WebAssembly global 'g' has an initial value but is not defined + .globaltype g, i32 + .globalinit g, i32.const 5 + +# Also for a value equal to the type's default. +# CHECK: error: WebAssembly global 'z' has an initial value but is not defined + .globaltype z, i32 + .globalinit z, i32.const 0 + + .functype use () -> (i32) + .globl use +use: + .functype use () -> (i32) + global.get g + global.get z + i32.add + end_function diff --git a/llvm/test/MC/WebAssembly/globalinit.s b/llvm/test/MC/WebAssembly/globalinit.s new file mode 100644 index 0000000000000..d96340cd85a22 --- /dev/null +++ b/llvm/test/MC/WebAssembly/globalinit.s @@ -0,0 +1,274 @@ +# RUN: llvm-mc -triple=wasm32-unknown-unknown -filetype=obj %s -o %t.o +# RUN: obj2yaml %t.o | FileCheck %s --check-prefix=OBJ +# RUN: llvm-mc -triple=wasm32-unknown-unknown %s | llvm-mc -triple=wasm32-unknown-unknown -filetype=obj -o - | obj2yaml | FileCheck %s --check-prefix=OBJ +# RUN: llvm-mc -triple=wasm32-unknown-unknown %s | FileCheck %s --check-prefix=ASM + + .globaltype i32g, i32 + .globalinit i32g, i32.const 33554435 + .globl i32g +i32g: + + .globaltype i64g, i64 + .globalinit i64g, i64.const -2 + .globl i64g +i64g: + + .globaltype f32g, f32 + .globalinit f32g, f32.const 0x1p1 + .globl f32g +f32g: + + .globaltype f64g, f64 + .globalinit f64g, f64.const -0x0p0 + .globl f64g +f64g: + + .globaltype nang, f32 + .globalinit nang, f32.const nan + .globl nang +nang: + + .globaltype constg, i32, immutable + .globalinit constg, i32.const 42 + .globl constg +constg: + +# Either spelling of a value that fits the type's width is accepted. + + .globaltype u32max, i32 + .globalinit u32max, i32.const 4294967295 + .globl u32max +u32max: + + .globaltype i32min, i32 + .globalinit i32min, i32.const -2147483648 + .globl i32min +i32min: + + .globaltype i32minu, i32 + .globalinit i32minu, i32.const 2147483648 + .globl i32minu +i32minu: + + .globaltype u64max, i64 + .globalinit u64max, i64.const 18446744073709551615 + .globl u64max +u64max: + +# Repeating a declaration is idempotent, both for the type and for the value. + + .globaltype retyped, i32 + .globalinit retyped, i32.const 7 + .globaltype retyped, i32 + .globl retyped +retyped: + + .globaltype twice, i32 + .globalinit twice, i32.const 9 + .globalinit twice, i32.const 9 + .globl twice +twice: + +# A repeat is compared on the union member its opcode selects, so a wider value +# written for another symbol in between cannot make it look like a conflict. + + .globaltype wide, f64 + .globalinit wide, f64.const 0x1.23456789abcdep100 + .globl wide +wide: + + .globaltype narrow, i32 + .globalinit narrow, i32.const 3 + .globalinit wide, f64.const 0x1.23456789abcdep100 + .globalinit narrow, i32.const 3 + .globl narrow +narrow: + + .globaltype inf32, f32 + .globalinit inf32, f32.const infinity + .globl inf32 +inf32: + + .globaltype dec32, f32 + .globalinit dec32, f32.const 0.5 + .globl dec32 +dec32: + +# A negative NaN keeps its sign. + + .globaltype nneg64, f64 + .globalinit nneg64, f64.const -nan + .globl nneg64 +nneg64: + + .globaltype nneg32, f32 + .globalinit nneg32, f32.const -nan + .globl nneg32 +nneg32: + +# A value equal to the type's default is printed back. + + .globaltype zerog, i32 + .globalinit zerog, i32.const 0 + .globl zerog +zerog: + +# ASM: .globaltype i32g, i32 +# ASM-NEXT: .globalinit i32g, i32.const 33554435 +# ASM: .globaltype i64g, i64 +# ASM-NEXT: .globalinit i64g, i64.const -2 +# ASM: .globaltype f32g, f32 +# ASM-NEXT: .globalinit f32g, f32.const 0x1p1 +# ASM: .globaltype f64g, f64 +# ASM-NEXT: .globalinit f64g, f64.const -0x0p0 +# ASM: .globaltype nang, f32 +# ASM-NEXT: .globalinit nang, f32.const nan +# ASM: .globaltype constg, i32, immutable +# ASM-NEXT: .globalinit constg, i32.const 42 +# ASM: .globaltype u32max, i32 +# ASM-NEXT: .globalinit u32max, i32.const -1 +# ASM: .globaltype i32min, i32 +# ASM-NEXT: .globalinit i32min, i32.const -2147483648 +# ASM: .globaltype i32minu, i32 +# ASM-NEXT: .globalinit i32minu, i32.const -2147483648 +# ASM: .globaltype u64max, i64 +# ASM-NEXT: .globalinit u64max, i64.const -1 +# ASM: .globaltype retyped, i32 +# ASM-NEXT: .globalinit retyped, i32.const 7 +# ASM-NEXT: .globaltype retyped, i32 +# ASM: .globaltype twice, i32 +# ASM-NEXT: .globalinit twice, i32.const 9 +# ASM-NEXT: .globalinit twice, i32.const 9 +# ASM: .globaltype wide, f64 +# ASM-NEXT: .globalinit wide, f64.const 0x1.23456789abcdep100 +# ASM: .globaltype narrow, i32 +# ASM-NEXT: .globalinit narrow, i32.const 3 +# ASM-NEXT: .globalinit wide, f64.const 0x1.23456789abcdep100 +# ASM-NEXT: .globalinit narrow, i32.const 3 +# ASM: .globaltype inf32, f32 +# ASM-NEXT: .globalinit inf32, f32.const infinity +# ASM: .globaltype dec32, f32 +# ASM-NEXT: .globalinit dec32, f32.const 0x1p-1 +# ASM: .globaltype nneg64, f64 +# ASM-NEXT: .globalinit nneg64, f64.const -nan +# ASM: .globaltype nneg32, f32 +# ASM-NEXT: .globalinit nneg32, f32.const -nan +# ASM: .globaltype zerog, i32 +# ASM-NEXT: .globalinit zerog, i32.const 0 + +# OBJ: - Type: GLOBAL +# OBJ-NEXT: Globals: +# OBJ-NEXT: - Index: 0 +# OBJ-NEXT: Type: I32 +# OBJ-NEXT: Mutable: true +# OBJ-NEXT: InitExpr: +# OBJ-NEXT: Opcode: I32_CONST +# OBJ-NEXT: Value: 33554435 +# OBJ-NEXT: - Index: 1 +# OBJ-NEXT: Type: I64 +# OBJ-NEXT: Mutable: true +# OBJ-NEXT: InitExpr: +# OBJ-NEXT: Opcode: I64_CONST +# OBJ-NEXT: Value: -2 +# OBJ-NEXT: - Index: 2 +# OBJ-NEXT: Type: F32 +# OBJ-NEXT: Mutable: true +# OBJ-NEXT: InitExpr: +# OBJ-NEXT: Opcode: F32_CONST +# OBJ-NEXT: Value: 1073741824 +# OBJ-NEXT: - Index: 3 +# OBJ-NEXT: Type: F64 +# OBJ-NEXT: Mutable: true +# OBJ-NEXT: InitExpr: +# OBJ-NEXT: Opcode: F64_CONST +# OBJ-NEXT: Value: 9223372036854775808 +# OBJ-NEXT: - Index: 4 +# OBJ-NEXT: Type: F32 +# OBJ-NEXT: Mutable: true +# OBJ-NEXT: InitExpr: +# OBJ-NEXT: Opcode: F32_CONST +# OBJ-NEXT: Value: 2143289344 +# OBJ-NEXT: - Index: 5 +# OBJ-NEXT: Type: I32 +# OBJ-NEXT: Mutable: false +# OBJ-NEXT: InitExpr: +# OBJ-NEXT: Opcode: I32_CONST +# OBJ-NEXT: Value: 42 +# OBJ-NEXT: - Index: 6 +# OBJ-NEXT: Type: I32 +# OBJ-NEXT: Mutable: true +# OBJ-NEXT: InitExpr: +# OBJ-NEXT: Opcode: I32_CONST +# OBJ-NEXT: Value: -1 +# OBJ-NEXT: - Index: 7 +# OBJ-NEXT: Type: I32 +# OBJ-NEXT: Mutable: true +# OBJ-NEXT: InitExpr: +# OBJ-NEXT: Opcode: I32_CONST +# OBJ-NEXT: Value: -2147483648 +# OBJ-NEXT: - Index: 8 +# OBJ-NEXT: Type: I32 +# OBJ-NEXT: Mutable: true +# OBJ-NEXT: InitExpr: +# OBJ-NEXT: Opcode: I32_CONST +# OBJ-NEXT: Value: -2147483648 +# OBJ-NEXT: - Index: 9 +# OBJ-NEXT: Type: I64 +# OBJ-NEXT: Mutable: true +# OBJ-NEXT: InitExpr: +# OBJ-NEXT: Opcode: I64_CONST +# OBJ-NEXT: Value: -1 +# OBJ-NEXT: - Index: 10 +# OBJ-NEXT: Type: I32 +# OBJ-NEXT: Mutable: true +# OBJ-NEXT: InitExpr: +# OBJ-NEXT: Opcode: I32_CONST +# OBJ-NEXT: Value: 7 +# OBJ-NEXT: - Index: 11 +# OBJ-NEXT: Type: I32 +# OBJ-NEXT: Mutable: true +# OBJ-NEXT: InitExpr: +# OBJ-NEXT: Opcode: I32_CONST +# OBJ-NEXT: Value: 9 +# OBJ-NEXT: - Index: 12 +# OBJ-NEXT: Type: F64 +# OBJ-NEXT: Mutable: true +# OBJ-NEXT: InitExpr: +# OBJ-NEXT: Opcode: F64_CONST +# OBJ-NEXT: Value: 5058162877485726942 +# OBJ-NEXT: - Index: 13 +# OBJ-NEXT: Type: I32 +# OBJ-NEXT: Mutable: true +# OBJ-NEXT: InitExpr: +# OBJ-NEXT: Opcode: I32_CONST +# OBJ-NEXT: Value: 3 +# OBJ-NEXT: - Index: 14 +# OBJ-NEXT: Type: F32 +# OBJ-NEXT: Mutable: true +# OBJ-NEXT: InitExpr: +# OBJ-NEXT: Opcode: F32_CONST +# OBJ-NEXT: Value: 2139095040 +# OBJ-NEXT: - Index: 15 +# OBJ-NEXT: Type: F32 +# OBJ-NEXT: Mutable: true +# OBJ-NEXT: InitExpr: +# OBJ-NEXT: Opcode: F32_CONST +# OBJ-NEXT: Value: 1056964608 +# OBJ-NEXT: - Index: 16 +# OBJ-NEXT: Type: F64 +# OBJ-NEXT: Mutable: true +# OBJ-NEXT: InitExpr: +# OBJ-NEXT: Opcode: F64_CONST +# OBJ-NEXT: Value: 18444492273895866368 +# OBJ-NEXT: - Index: 17 +# OBJ-NEXT: Type: F32 +# OBJ-NEXT: Mutable: true +# OBJ-NEXT: InitExpr: +# OBJ-NEXT: Opcode: F32_CONST +# OBJ-NEXT: Value: 4290772992 +# OBJ-NEXT: - Index: 18 +# OBJ-NEXT: Type: I32 +# OBJ-NEXT: Mutable: true +# OBJ-NEXT: InitExpr: +# OBJ-NEXT: Opcode: I32_CONST +# OBJ-NEXT: Value: 0 diff --git a/llvm/test/MC/WebAssembly/globaltype-unsupported.s b/llvm/test/MC/WebAssembly/globaltype-unsupported.s new file mode 100644 index 0000000000000..32f7ba3846f78 --- /dev/null +++ b/llvm/test/MC/WebAssembly/globaltype-unsupported.s @@ -0,0 +1,8 @@ +# RUN: not llvm-mc -triple=wasm32-unknown-unknown -filetype=obj %s -o /dev/null 2>&1 | FileCheck %s + +# The object writer also rejects a v128 global that comes from assembly. + +# CHECK: error: WebAssembly global 'g' has a type that cannot be initialized + .globaltype g, v128 + .globl g +g: _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
