From: Dan Gohman Date: Thu, 30 Mar 2017 23:58:19 +0000 (+0000) Subject: [WebAssembly] Initial linking metadata support X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=546a4623a537387f054179fa484a921cca531522;p=llvm [WebAssembly] Initial linking metadata support Add support for the new relocations and linking metadata section support in https://github.com/WebAssembly/tool-conventions/blob/master/Linking.md. In particular, this allows LLVM to indicate which variable is the stack pointer, so that it can be linked with other objects. This also adds support for emitting type relocations for call_indirect instructions. Right now, this is mainly tested by using wabt and hexdump to examine the output on selected testcases. We'll add more tests as the design stablizes and more of the pieces are in place. git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@299141 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/include/llvm/CodeGen/MachineModuleInfoImpls.h b/include/llvm/CodeGen/MachineModuleInfoImpls.h index 0dc190594c3..f28a79c5b5c 100644 --- a/include/llvm/CodeGen/MachineModuleInfoImpls.h +++ b/include/llvm/CodeGen/MachineModuleInfoImpls.h @@ -17,6 +17,7 @@ #include "llvm/CodeGen/ValueTypes.h" #include "llvm/CodeGen/MachineModuleInfo.h" +#include "llvm/Support/Wasm.h" namespace llvm { class MCSymbol; @@ -79,9 +80,10 @@ public: /// MachineModuleInfoWasm - This is a MachineModuleInfoImpl implementation /// for Wasm targets. class MachineModuleInfoWasm : public MachineModuleInfoImpl { - /// GVStubs - These stubs are used to materialize global addresses in PIC - /// mode. - std::vector Globals; + /// WebAssembly global variables defined by CodeGen. + std::vector Globals; + + /// The WebAssembly global variable which is the stack pointer. unsigned StackPointerGlobal; virtual void anchor(); // Out of line virtual method. @@ -89,8 +91,8 @@ public: MachineModuleInfoWasm(const MachineModuleInfo &) : StackPointerGlobal(-1U) {} - void addGlobal(MVT VT) { Globals.push_back(VT); } - const std::vector &getGlobals() const { return Globals; } + void addGlobal(const wasm::Global &G) { Globals.push_back(G); } + const std::vector &getGlobals() const { return Globals; } bool hasStackPointerGlobal() const { return StackPointerGlobal != -1U; diff --git a/include/llvm/Support/Wasm.h b/include/llvm/Support/Wasm.h index 99467ad3c63..8e6c418c818 100644 --- a/include/llvm/Support/Wasm.h +++ b/include/llvm/Support/Wasm.h @@ -170,6 +170,11 @@ enum class ValType { F64 = WASM_TYPE_F64, }; +// Linking metadata kinds. +enum : unsigned { + WASM_STACK_POINTER = 0x1, +}; + #define WASM_RELOC(name, value) name = value, enum : unsigned { @@ -178,6 +183,19 @@ enum : unsigned { #undef WASM_RELOC +struct Global { + ValType Type; + bool Mutable; + + // The initial value for this global is either the value of an imported + // global, in which case InitialModule and InitialName specify the global + // import, or a value, in which case InitialModule is empty and InitialValue + // holds the value. + StringRef InitialModule; + StringRef InitialName; + uint64_t InitialValue; +}; + } // end namespace wasm } // end namespace llvm diff --git a/include/llvm/Support/WasmRelocs/WebAssembly.def b/include/llvm/Support/WasmRelocs/WebAssembly.def index 798f74499fa..da64e025478 100644 --- a/include/llvm/Support/WasmRelocs/WebAssembly.def +++ b/include/llvm/Support/WasmRelocs/WebAssembly.def @@ -9,3 +9,5 @@ WASM_RELOC(R_WEBASSEMBLY_TABLE_INDEX_I32, 2) WASM_RELOC(R_WEBASSEMBLY_GLOBAL_ADDR_LEB, 3) WASM_RELOC(R_WEBASSEMBLY_GLOBAL_ADDR_SLEB, 4) WASM_RELOC(R_WEBASSEMBLY_GLOBAL_ADDR_I32, 5) +WASM_RELOC(R_WEBASSEMBLY_TYPE_INDEX_LEB, 6) +WASM_RELOC(R_WEBASSEMBLY_GLOBAL_INDEX_LEB, 7) diff --git a/lib/MC/WasmObjectWriter.cpp b/lib/MC/WasmObjectWriter.cpp index 0b703f4387b..548d99fe4b9 100644 --- a/lib/MC/WasmObjectWriter.cpp +++ b/lib/MC/WasmObjectWriter.cpp @@ -50,16 +50,6 @@ struct SectionBookkeeping { uint64_t ContentsOffset; }; -// This record records information about a call_indirect which needs its -// type index fixed up once we've computed type indices. -struct TypeIndexFixup { - uint64_t Offset; - const MCSymbolWasm *Symbol; - const MCSectionWasm *FixupSection; - TypeIndexFixup(uint64_t O, const MCSymbolWasm *S, MCSectionWasm *F) - : Offset(O), Symbol(S), FixupSection(F) {} -}; - class WasmObjectWriter : public MCObjectWriter { /// Helper struct for containing some precomputed information on symbols. struct WasmSymbolData { @@ -80,7 +70,7 @@ class WasmObjectWriter : public MCObjectWriter { std::vector DataRelocations; // Fixups for call_indirect type indices. - std::vector TypeIndexFixups; + std::vector TypeIndexFixups; // Index values to use for fixing up call_indirect type indices. std::vector TypeIndexFixupTypes; @@ -269,8 +259,11 @@ void WasmObjectWriter::recordRelocation(MCAssembler &Asm, if (RefA) { if (RefA->getKind() == MCSymbolRefExpr::VK_WebAssembly_TYPEINDEX) { - TypeIndexFixups.push_back(TypeIndexFixup(FixupOffset, SymA, - &FixupSection)); + assert(C == 0); + WasmRelocationEntry Rec(FixupOffset, SymA, C, + wasm::R_WEBASSEMBLY_TYPE_INDEX_LEB, + &FixupSection); + TypeIndexFixups.push_back(Rec); return; } } @@ -358,7 +351,9 @@ struct WasmExport { struct WasmGlobal { wasm::ValType Type; bool IsMutable; - uint32_t InitialValue; + bool HasImport; + uint64_t InitialValue; + uint32_t ImportIndex; }; } // end anonymous namespace @@ -507,6 +502,29 @@ static void WriteRelocations( } } +// Write out the the type relocation records that the linker will +// need to handle. +static void WriteTypeRelocations( + ArrayRef TypeIndexFixups, + ArrayRef TypeIndexFixupTypes, + raw_pwrite_stream &Stream) +{ + for (size_t i = 0, e = TypeIndexFixups.size(); i < e; ++i) { + const WasmRelocationEntry &Fixup = TypeIndexFixups[i]; + uint32_t Type = TypeIndexFixupTypes[i]; + + assert(Fixup.Type == wasm::R_WEBASSEMBLY_TYPE_INDEX_LEB); + assert(Fixup.Addend == 0); + + uint64_t Offset = Fixup.Offset + + Fixup.FixupSection->getSectionOffset(); + + encodeULEB128(Fixup.Type, Stream); + encodeULEB128(Offset, Stream); + encodeULEB128(Type, Stream); + } +} + void WasmObjectWriter::writeObject(MCAssembler &Asm, const MCAsmLayout &Layout) { MCContext &Ctx = Asm.getContext(); @@ -526,6 +544,8 @@ void WasmObjectWriter::writeObject(MCAssembler &Asm, unsigned NumFuncImports = 0; unsigned NumGlobalImports = 0; SmallVector DataBytes; + uint32_t StackPointerGlobal = 0; + bool HasStackPointer = false; // Populate the IsAddressTaken set. for (WasmRelocationEntry RelEntry : CodeRelocations) { @@ -605,15 +625,68 @@ void WasmObjectWriter::writeObject(MCAssembler &Asm, if (!DataFrag.getFixups().empty()) report_fatal_error("fixups not supported in .global_variables"); const SmallVectorImpl &Contents = DataFrag.getContents(); - for (char p : Contents) { + for (const uint8_t *p = (const uint8_t *)Contents.data(), + *end = (const uint8_t *)Contents.data() + Contents.size(); + p != end; ) { WasmGlobal G; - G.Type = wasm::ValType(p); - G.IsMutable = true; - G.InitialValue = 0; + if (end - p < 3) + report_fatal_error("truncated global variable encoding"); + G.Type = wasm::ValType(int8_t(*p++)); + G.IsMutable = bool(*p++); + G.HasImport = bool(*p++); + if (G.HasImport) { + G.InitialValue = 0; + + WasmImport Import; + Import.ModuleName = (const char *)p; + const uint8_t *nul = (const uint8_t *)memchr(p, '\0', end - p); + if (!nul) + report_fatal_error("global module name must be nul-terminated"); + p = nul + 1; + nul = (const uint8_t *)memchr(p, '\0', end - p); + if (!nul) + report_fatal_error("global base name must be nul-terminated"); + Import.FieldName = (const char *)p; + p = nul + 1; + + Import.Kind = wasm::WASM_EXTERNAL_GLOBAL; + Import.Type = int32_t(G.Type); + + G.ImportIndex = NumGlobalImports; + ++NumGlobalImports; + + Imports.push_back(Import); + } else { + unsigned n; + G.InitialValue = decodeSLEB128(p, &n); + G.ImportIndex = 0; + if (n > end - p) + report_fatal_error("global initial value must be valid SLEB128"); + p += n; + } Globals.push_back(G); } } + // In the special .stack_pointer section, we've encoded the stack pointer + // index. + MCSectionWasm *StackPtr = Ctx.getWasmSection(".stack_pointer", 0, 0); + if (!StackPtr->getFragmentList().empty()) { + if (StackPtr->getFragmentList().size() != 1) + report_fatal_error("only one .stack_pointer fragment supported"); + const MCFragment &Frag = *StackPtr->begin(); + if (Frag.hasInstructions() || Frag.getKind() != MCFragment::FT_Data) + report_fatal_error("only data supported in .stack_pointer"); + const MCDataFragment &DataFrag = cast(Frag); + if (!DataFrag.getFixups().empty()) + report_fatal_error("fixups not supported in .stack_pointer"); + const SmallVectorImpl &Contents = DataFrag.getContents(); + if (Contents.size() != 4) + report_fatal_error("only one entry supported in .stack_pointer"); + HasStackPointer = true; + StackPointerGlobal = NumGlobalImports + *(const int32_t *)Contents.data(); + } + // Handle defined symbols. for (const MCSymbol &S : Asm.symbols()) { // Ignore unnamed temporary symbols, which aren't ever exported, imported, @@ -712,7 +785,9 @@ void WasmObjectWriter::writeObject(MCAssembler &Asm, WasmGlobal Global; Global.Type = PtrType; Global.IsMutable = false; + Global.HasImport = false; Global.InitialValue = DataSection.getSectionOffset(); + Global.ImportIndex = 0; SymbolIndices[&WS] = Index; Globals.push_back(Global); } @@ -736,7 +811,10 @@ void WasmObjectWriter::writeObject(MCAssembler &Asm, } // Add types for indirect function calls. - for (const TypeIndexFixup &Fixup : TypeIndexFixups) { + for (const WasmRelocationEntry &Fixup : TypeIndexFixups) { + assert(Fixup.Addend == 0); + assert(Fixup.Type == wasm::R_WEBASSEMBLY_TYPE_INDEX_LEB); + WasmFunctionType F; F.Returns = Fixup.Symbol->getReturns(); F.Params = Fixup.Symbol->getParams(); @@ -793,7 +871,7 @@ void WasmObjectWriter::writeObject(MCAssembler &Asm, encodeULEB128(Import.Type, getStream()); break; case wasm::WASM_EXTERNAL_GLOBAL: - encodeSLEB128(Import.Type, getStream()); + encodeSLEB128(int32_t(Import.Type), getStream()); encodeULEB128(0, getStream()); // mutability break; default: @@ -853,8 +931,15 @@ void WasmObjectWriter::writeObject(MCAssembler &Asm, writeValueType(Global.Type); write8(Global.IsMutable); - write8(wasm::WASM_OPCODE_I32_CONST); - encodeSLEB128(Global.InitialValue, getStream()); // offset + if (Global.HasImport) { + assert(Global.InitialValue == 0); + write8(wasm::WASM_OPCODE_GET_GLOBAL); + encodeULEB128(Global.ImportIndex, getStream()); + } else { + assert(Global.ImportIndex == 0); + write8(wasm::WASM_OPCODE_I32_CONST); + encodeSLEB128(Global.InitialValue, getStream()); // offset + } write8(wasm::WASM_OPCODE_END); } @@ -944,7 +1029,9 @@ void WasmObjectWriter::writeObject(MCAssembler &Asm, uint32_t Type = TypeIndexFixupTypes[i]; unsigned Padding = PaddingFor5ByteULEB128(Type); - const TypeIndexFixup &Fixup = TypeIndexFixups[i]; + const WasmRelocationEntry &Fixup = TypeIndexFixups[i]; + assert(Fixup.Addend == 0); + assert(Fixup.Type == wasm::R_WEBASSEMBLY_TYPE_INDEX_LEB); uint64_t Offset = Fixup.Offset + Fixup.FixupSection->getSectionOffset(); @@ -1021,6 +1108,7 @@ void WasmObjectWriter::writeObject(MCAssembler &Asm, encodeULEB128(CodeRelocations.size(), getStream()); WriteRelocations(CodeRelocations, getStream(), SymbolIndices); + WriteTypeRelocations(TypeIndexFixups, TypeIndexFixupTypes, getStream()); endSection(Section); } @@ -1038,6 +1126,18 @@ void WasmObjectWriter::writeObject(MCAssembler &Asm, endSection(Section); } + // === Linking Metadata Section ============================================== + if (HasStackPointer) { + startSection(Section, wasm::WASM_SEC_CUSTOM, "linking"); + + encodeULEB128(1, getStream()); // count + + encodeULEB128(wasm::WASM_STACK_POINTER, getStream()); // type + encodeULEB128(StackPointerGlobal, getStream()); // id + + endSection(Section); + } + // TODO: Translate the .comment section to the output. // TODO: Translate debug sections to the output. diff --git a/lib/Target/WebAssembly/InstPrinter/WebAssemblyInstPrinter.cpp b/lib/Target/WebAssembly/InstPrinter/WebAssemblyInstPrinter.cpp index 0af13cffdb0..26461e2728d 100644 --- a/lib/Target/WebAssembly/InstPrinter/WebAssemblyInstPrinter.cpp +++ b/lib/Target/WebAssembly/InstPrinter/WebAssemblyInstPrinter.cpp @@ -242,3 +242,18 @@ const char *llvm::WebAssembly::TypeToString(MVT Ty) { llvm_unreachable("unsupported type"); } } + +const char *llvm::WebAssembly::TypeToString(wasm::ValType Type) { + switch (Type) { + case wasm::ValType::I32: + return "i32"; + case wasm::ValType::I64: + return "i64"; + case wasm::ValType::F32: + return "f32"; + case wasm::ValType::F64: + return "f64"; + default: + llvm_unreachable("unsupported type"); + } +} diff --git a/lib/Target/WebAssembly/InstPrinter/WebAssemblyInstPrinter.h b/lib/Target/WebAssembly/InstPrinter/WebAssemblyInstPrinter.h index d11f99c1ff3..c6158720d62 100644 --- a/lib/Target/WebAssembly/InstPrinter/WebAssemblyInstPrinter.h +++ b/lib/Target/WebAssembly/InstPrinter/WebAssemblyInstPrinter.h @@ -18,6 +18,7 @@ #include "llvm/ADT/SmallVector.h" #include "llvm/CodeGen/MachineValueType.h" #include "llvm/MC/MCInstPrinter.h" +#include "llvm/Support/Wasm.h" namespace llvm { @@ -50,6 +51,7 @@ public: namespace WebAssembly { const char *TypeToString(MVT Ty); +const char *TypeToString(wasm::ValType Type); } // end namespace WebAssembly diff --git a/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyTargetStreamer.cpp b/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyTargetStreamer.cpp index f3b6b100d65..ad59f2f4058 100644 --- a/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyTargetStreamer.cpp +++ b/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyTargetStreamer.cpp @@ -25,7 +25,6 @@ #include "llvm/Support/Casting.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/FormattedStream.h" -#include "llvm/Support/Wasm.h" using namespace llvm; WebAssemblyTargetStreamer::WebAssemblyTargetStreamer(MCStreamer &S) @@ -88,13 +87,31 @@ void WebAssemblyTargetAsmStreamer::emitLocal(ArrayRef Types) { } } -void WebAssemblyTargetAsmStreamer::emitGlobal(ArrayRef Types) { - if (!Types.empty()) { +void WebAssemblyTargetAsmStreamer::emitGlobal( + ArrayRef Globals) { + if (!Globals.empty()) { OS << "\t.globalvar \t"; - PrintTypes(OS, Types); + + bool First = true; + for (const wasm::Global &G : Globals) { + if (First) + First = false; + else + OS << ", "; + OS << WebAssembly::TypeToString(G.Type); + if (!G.InitialModule.empty()) + OS << '=' << G.InitialModule << ':' << G.InitialName; + else + OS << '=' << G.InitialValue; + } + OS << '\n'; } } +void WebAssemblyTargetAsmStreamer::emitStackPointer(uint32_t Index) { + OS << "\t.stack_pointer\t" << Index << '\n'; +} + void WebAssemblyTargetAsmStreamer::emitEndFunc() { OS << "\t.endfunc\n"; } void WebAssemblyTargetAsmStreamer::emitIndirectFunctionType( @@ -135,10 +152,16 @@ void WebAssemblyTargetELFStreamer::emitLocal(ArrayRef Types) { emitValueType(WebAssembly::toValType(Type)); } -void WebAssemblyTargetELFStreamer::emitGlobal(ArrayRef Types) { +void WebAssemblyTargetELFStreamer::emitGlobal( + ArrayRef Globals) { llvm_unreachable(".globalvar encoding not yet implemented"); } +void WebAssemblyTargetELFStreamer::emitStackPointer( + uint32_t Index) { + llvm_unreachable(".stack_pointer encoding not yet implemented"); +} + void WebAssemblyTargetELFStreamer::emitEndFunc() { Streamer.EmitIntValue(WebAssembly::End, 1); } @@ -190,15 +213,36 @@ void WebAssemblyTargetWasmStreamer::emitLocal(ArrayRef Types) { } } -void WebAssemblyTargetWasmStreamer::emitGlobal(ArrayRef Types) { +void WebAssemblyTargetWasmStreamer::emitGlobal( + ArrayRef Globals) { // Encode the globals use by the funciton into the special .global_variables // section. This will later be decoded and turned into contents for the // Globals Section. Streamer.PushSection(); Streamer.SwitchSection(Streamer.getContext() .getWasmSection(".global_variables", 0, 0)); - for (MVT Ty : Types) - Streamer.EmitIntValue(int64_t(WebAssembly::toValType(Ty)), 1); + for (const wasm::Global &G : Globals) { + Streamer.EmitIntValue(int32_t(G.Type), 1); + Streamer.EmitIntValue(G.Mutable, 1); + if (G.InitialModule.empty()) { + Streamer.EmitIntValue(0, 1); // indicate that we have an int value + Streamer.EmitSLEB128IntValue(0); + } else { + Streamer.EmitIntValue(1, 1); // indicate that we have a module import + Streamer.EmitBytes(G.InitialModule); + Streamer.EmitIntValue(0, 1); // nul-terminate + Streamer.EmitBytes(G.InitialName); + Streamer.EmitIntValue(0, 1); // nul-terminate + } + } + Streamer.PopSection(); +} + +void WebAssemblyTargetWasmStreamer::emitStackPointer(uint32_t Index) { + Streamer.PushSection(); + Streamer.SwitchSection(Streamer.getContext() + .getWasmSection(".stack_pointer", 0, 0)); + Streamer.EmitIntValue(Index, 4); Streamer.PopSection(); } diff --git a/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyTargetStreamer.h b/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyTargetStreamer.h index bb9a9c90c3a..68d6747298d 100644 --- a/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyTargetStreamer.h +++ b/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyTargetStreamer.h @@ -38,7 +38,9 @@ public: /// .local virtual void emitLocal(ArrayRef Types) = 0; /// .globalvar - virtual void emitGlobal(ArrayRef Types) = 0; + virtual void emitGlobal(ArrayRef Globals) = 0; + /// .stack_pointer + virtual void emitStackPointer(uint32_t Index) = 0; /// .endfunc virtual void emitEndFunc() = 0; /// .functype @@ -66,7 +68,8 @@ public: void emitParam(MCSymbol *Symbol, ArrayRef Types) override; void emitResult(MCSymbol *Symbol, ArrayRef Types) override; void emitLocal(ArrayRef Types) override; - void emitGlobal(ArrayRef Types) override; + void emitGlobal(ArrayRef Globals) override; + void emitStackPointer(uint32_t Index) override; void emitEndFunc() override; void emitIndirectFunctionType(StringRef name, SmallVectorImpl &Params, @@ -83,7 +86,8 @@ public: void emitParam(MCSymbol *Symbol, ArrayRef Types) override; void emitResult(MCSymbol *Symbol, ArrayRef Types) override; void emitLocal(ArrayRef Types) override; - void emitGlobal(ArrayRef Types) override; + void emitGlobal(ArrayRef Globals) override; + void emitStackPointer(uint32_t Index) override; void emitEndFunc() override; void emitIndirectFunctionType(StringRef name, SmallVectorImpl &Params, @@ -100,7 +104,8 @@ public: void emitParam(MCSymbol *Symbol, ArrayRef Types) override; void emitResult(MCSymbol *Symbol, ArrayRef Types) override; void emitLocal(ArrayRef Types) override; - void emitGlobal(ArrayRef Types) override; + void emitGlobal(ArrayRef Globals) override; + void emitStackPointer(uint32_t Index) override; void emitEndFunc() override; void emitIndirectFunctionType(StringRef name, SmallVectorImpl &Params, diff --git a/lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp b/lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp index c272eb4cbf5..d9c2dba5bac 100644 --- a/lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp +++ b/lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp @@ -97,6 +97,8 @@ void WebAssemblyAsmPrinter::EmitEndOfAsmFile(Module &M) { if (!TM.getTargetTriple().isOSBinFormatELF()) { MachineModuleInfoWasm &MMIW = MMI->getObjFileInfo(); getTargetStreamer()->emitGlobal(MMIW.getGlobals()); + if (MMIW.hasStackPointerGlobal()) + getTargetStreamer()->emitStackPointer(MMIW.getStackPointerGlobal()); } } diff --git a/lib/Target/WebAssembly/WebAssemblyFrameLowering.cpp b/lib/Target/WebAssembly/WebAssemblyFrameLowering.cpp index 8b25285ad0d..4209bc333f2 100644 --- a/lib/Target/WebAssembly/WebAssemblyFrameLowering.cpp +++ b/lib/Target/WebAssembly/WebAssemblyFrameLowering.cpp @@ -192,7 +192,16 @@ void WebAssemblyFrameLowering::emitPrologue(MachineFunction &MF, auto &MMIW = MF.getMMI().getObjFileInfo(); if (!MMIW.hasStackPointerGlobal()) { MMIW.setStackPointerGlobal(MMIW.getGlobals().size()); - MMIW.addGlobal(MVT::i32); + + // Create the stack-pointer global. For now, just use the + // Emscripten/Binaryen ABI names. + wasm::Global G; + G.Type = wasm::ValType::I32; + G.Mutable = true; + G.InitialValue = 0; + G.InitialModule = "env"; + G.InitialName = "STACKTOP"; + MMIW.addGlobal(G); } BuildMI(MBB, InsertPt, DL, TII->get(WebAssembly::GET_GLOBAL_I32), SPReg) .addImm(MMIW.getStackPointerGlobal());