Commit c5abdc13 by Karl Schimpf

Start incorporating the ARM integrated assembler.

Extends the ARM32 assembler to be able to generate a trivial function footprint using the -filetype=iasm option. Also does a couple of cleanups: 1) Move UnimplementedError macro to common location so that it can be used by everyone. 2) Add a GlobalContext argument to the assembler, so that it can look at flags etc. BUG= https://code.google.com/p/nativeclient/issues/detail?id=4334 R=stichnot@chromium.org Review URL: https://codereview.chromium.org/1397933002 .
parent e7418719
......@@ -196,6 +196,7 @@ SB_LDFLAGS := $(LINKOPTLEVEL) $(LD_EXTRA)
SRCS = \
IceAssembler.cpp \
IceAssemblerARM32.cpp \
IceBrowserCompileServer.cpp \
IceCfg.cpp \
IceCfgNode.cpp \
......
......@@ -1433,9 +1433,12 @@ void Assembler::vcgtqs(QRegister qd, QRegister qn, QRegister qm) {
}
#if 0
// Moved to: ARM32::AssemblerARM32.
void Assembler::bkpt(uint16_t imm16) {
Emit(BkptEncoding(imm16));
}
#endif
void Assembler::b(Label* label, Condition cond) {
......@@ -1447,7 +1450,8 @@ void Assembler::bl(Label* label, Condition cond) {
EmitBranch(cond, label, true);
}
#if 0
// Moved to: ARM32::AssemblerARM32.
void Assembler::bx(Register rm, Condition cond) {
ASSERT(rm != kNoRegister);
ASSERT(cond != kNoCondition);
......@@ -1456,6 +1460,7 @@ void Assembler::bx(Register rm, Condition cond) {
(static_cast<int32_t>(rm) << kRmShift);
Emit(encoding);
}
#endif
void Assembler::blx(Register rm, Condition cond) {
......@@ -2314,7 +2319,10 @@ void Assembler::BindARMv6(Label* label) {
label->BindTo(bound_pc);
}
#if 0
// Moved to: ARM32::AssemblerARM32 as method bind(Label* Label)
// Note: Most of this code isn't needed because instruction selection has
// already been handler
void Assembler::BindARMv7(Label* label) {
ASSERT(!label->IsBound());
intptr_t bound_pc = buffer_.Size();
......@@ -2381,6 +2389,7 @@ void Assembler::BindARMv7(Label* label) {
}
label->BindTo(bound_pc);
}
#endif
void Assembler::Bind(Label* label) {
......
......@@ -27,7 +27,8 @@ namespace dart {
class RuntimeEntry;
class StubEntry;
#if 0
// Moved to: ARM32::AssemblerARM32.
// Instruction encoding bits.
enum {
H = 1 << 5, // halfword (or byte)
......@@ -68,7 +69,7 @@ enum {
B26 = 1 << 26,
B27 = 1 << 27,
};
#endif
class Label : public ValueObject {
public:
......@@ -532,6 +533,8 @@ class Assembler : public ValueObject {
void clrex();
void nop(Condition cond = AL);
#if 0
// Moved to: ARM32::AssemblerARM32.
// Note that gdb sets breakpoints using the undefined instruction 0xe7f001f0.
void bkpt(uint16_t imm16);
......@@ -540,6 +543,7 @@ class Assembler : public ValueObject {
return (AL << kConditionShift) | B24 | B21 |
((imm16 >> 4) << 8) | B6 | B5 | B4 | (imm16 & 0xf);
}
#endif
static uword GetBreakInstructionFiller() {
return BkptEncoding(0);
......@@ -660,7 +664,10 @@ class Assembler : public ValueObject {
// Branch instructions.
void b(Label* label, Condition cond = AL);
void bl(Label* label, Condition cond = AL);
#if 0
// Moved to: ARM32::AssemblerARM32.
void bx(Register rm, Condition cond = AL);
#endif
void blx(Register rm, Condition cond = AL);
void Branch(const StubEntry& stub_entry,
......
......@@ -119,7 +119,7 @@ llvm::StringRef Assembler::getBufferView() const {
Buffer.size());
}
void Assembler::emitIASBytes(GlobalContext *Ctx) const {
void Assembler::emitIASBytes() const {
Ostream &Str = Ctx->getStrEmit();
intptr_t EndPosition = Buffer.size();
intptr_t CurPosition = 0;
......
......@@ -64,17 +64,20 @@ public:
return Position - kWordSize;
}
void setPosition(intptr_t NewValue) { Position = NewValue; }
bool isBound() const { return Position < 0; }
bool isLinked() const { return Position > 0; }
virtual bool isUnused() const { return Position == 0; }
protected:
void bindTo(intptr_t position) {
assert(!isBound());
Position = -position - kWordSize;
assert(isBound());
}
protected:
void linkTo(intptr_t position) {
assert(!isBound());
Position = position + kWordSize;
......@@ -83,7 +86,6 @@ protected:
intptr_t Position = 0;
private:
// TODO(jvoung): why are labels offset by this?
static constexpr uint32_t kWordSize = sizeof(uint32_t);
};
......@@ -272,7 +274,7 @@ public:
return Buffer.createFixup(Kind, Value);
}
void emitIASBytes(GlobalContext *Ctx) const;
void emitIASBytes() const;
bool getInternal() const { return IsInternal; }
void setInternal(bool Internal) { IsInternal = Internal; }
const IceString &getFunctionName() { return FunctionName; }
......@@ -286,8 +288,8 @@ public:
AssemblerKind getKind() const { return Kind; }
protected:
explicit Assembler(AssemblerKind Kind)
: Kind(Kind), Allocator(), Buffer(*this) {}
explicit Assembler(AssemblerKind Kind, GlobalContext *Ctx)
: Kind(Kind), Allocator(), Ctx(Ctx), Buffer(*this) {}
private:
const AssemblerKind Kind;
......@@ -305,6 +307,7 @@ private:
bool Preliminary = false;
protected:
GlobalContext *Ctx;
// Buffer's constructor uses the Allocator, so it needs to appear after it.
// TODO(jpp): dependencies on construction order are a nice way of shooting
// yourself in the foot. Fix this.
......
//===- subzero/src/IceAssemblerARM32.cpp - Assembler for ARM32 --*- C++ -*-===//
//
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
//
// Modified by the Subzero authors.
//
//===----------------------------------------------------------------------===//
//
// The Subzero Code Generator
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
///
/// \file
/// This file implements the Assembler class for ARM32.
///
//===----------------------------------------------------------------------===//
#include "IceAssemblerARM32.h"
namespace Ice {
Label *ARM32::AssemblerARM32::getOrCreateLabel(SizeT Number,
LabelVector &Labels) {
Label *L = nullptr;
if (Number == Labels.size()) {
L = new (this->allocate<Label>()) Label();
Labels.push_back(L);
return L;
}
if (Number > Labels.size()) {
Labels.resize(Number + 1);
}
L = Labels[Number];
if (!L) {
L = new (this->allocate<Label>()) Label();
Labels[Number] = L;
}
return L;
}
void ARM32::AssemblerARM32::bind(Label *label) {
intptr_t bound = Buffer.size();
assert(!label->isBound()); // Labels can only be bound once.
while (label->isLinked()) {
intptr_t position = label->getLinkPosition();
intptr_t next = Buffer.load<int32_t>(position);
Buffer.store<int32_t>(position, bound - (position + 4));
label->setPosition(next);
}
// TODO(kschimpf) Decide if we have near jumps.
label->bindTo(bound);
}
void ARM32::AssemblerARM32::bkpt(uint16_t imm16) {
AssemblerBuffer::EnsureCapacity ensured(&Buffer);
emitInt32(BkptEncoding(imm16));
}
void ARM32::AssemblerARM32::bx(RegARM32::GPRRegister rm, CondARM32::Cond cond) {
// cccc000100101111111111110001mmmm where mmmm=rm and cccc=Cond.
assert(rm != RegARM32::Encoded_Not_GPR);
assert(cond != CondARM32::kNone);
AssemblerBuffer::EnsureCapacity ensured(&Buffer);
int32_t encoding = (static_cast<int32_t>(cond) << kConditionShift) | B24 |
B21 | (0xfff << 8) | B4 |
(static_cast<int32_t>(rm) << kRmShift);
emitInt32(encoding);
}
} // end of namespace Ice
......@@ -24,8 +24,11 @@
#define SUBZERO_SRC_ICEASSEMBLERARM32_H
#include "IceAssembler.h"
#include "IceConditionCodesARM32.h"
#include "IceDefs.h"
#include "IceFixups.h"
#include "IceRegistersARM32.h"
#include "IceTargetLowering.h"
namespace Ice {
namespace ARM32 {
......@@ -35,15 +38,24 @@ class AssemblerARM32 : public Assembler {
AssemblerARM32 &operator=(const AssemblerARM32 &) = delete;
public:
explicit AssemblerARM32(bool use_far_branches = false)
: Assembler(Asm_ARM32) {
// This mode is only needed and implemented for MIPS and ARM.
assert(!use_far_branches);
explicit AssemblerARM32(GlobalContext *Ctx, bool use_far_branches = false)
: Assembler(Asm_ARM32, Ctx) {
// TODO(kschimpf): Add mode if needed when branches are handled.
(void)use_far_branches;
}
~AssemblerARM32() override = default;
void alignFunction() override { llvm_unreachable("Not yet implemented."); }
void alignFunction() override {
const SizeT Align = 1 << getBundleAlignLog2Bytes();
SizeT BytesNeeded = Utils::OffsetToAlignment(Buffer.getPosition(), Align);
constexpr SizeT InstSize = sizeof(int32_t);
assert(BytesNeeded % InstSize == 0);
while (BytesNeeded > 0) {
// TODO(kschimpf) Should this be NOP or some other instruction?
bkpt(0);
BytesNeeded -= InstSize;
}
}
SizeT getBundleAlignLog2Bytes() const override { return 4; }
......@@ -67,18 +79,173 @@ public:
}
void bindCfgNodeLabel(SizeT NodeNumber) override {
(void)NodeNumber;
llvm_unreachable("Not yet implemented.");
assert(!getPreliminary());
Label *L = getOrCreateCfgNodeLabel(NodeNumber);
this->bind(L);
}
bool fixupIsPCRel(FixupKind Kind) const override {
(void)Kind;
llvm_unreachable("Not yet implemented.");
}
void bind(Label *label);
void bkpt(uint16_t imm16);
void bx(RegARM32::GPRRegister rm, CondARM32::Cond cond = CondARM32::AL);
static bool classof(const Assembler *Asm) {
return Asm->getKind() == Asm_ARM32;
}
private:
// Instruction encoding bits.
// halfword (or byte)
static constexpr uint32_t H = 1 << 5;
// load (or store)
static constexpr uint32_t L = 1 << 20;
// set condition code (or leave unchanged)
static constexpr uint32_t S = 1 << 20;
// writeback base register (or leave unchanged)
static constexpr uint32_t W = 1 << 21;
// accumulate in multiply instruction (or not)
static constexpr uint32_t A = 1 << 21;
// unsigned byte (or word)
static constexpr uint32_t B = 1 << 22;
// high/lo bit of start of s/d register range
static constexpr uint32_t D = 1 << 22;
// long (or short)
static constexpr uint32_t N = 1 << 22;
// positive (or negative) offset/index
static constexpr uint32_t U = 1 << 23;
// offset/pre-indexed addressing (or post-indexed addressing)
static constexpr uint32_t P = 1 << 24;
// immediate shifter operand (or not)
static constexpr uint32_t I = 1 << 25;
// The following define individual bits.
static constexpr uint32_t B0 = 1;
static constexpr uint32_t B1 = 1 << 1;
static constexpr uint32_t B2 = 1 << 2;
static constexpr uint32_t B3 = 1 << 3;
static constexpr uint32_t B4 = 1 << 4;
static constexpr uint32_t B5 = 1 << 5;
static constexpr uint32_t B6 = 1 << 6;
static constexpr uint32_t B7 = 1 << 7;
static constexpr uint32_t B8 = 1 << 8;
static constexpr uint32_t B9 = 1 << 9;
static constexpr uint32_t B10 = 1 << 10;
static constexpr uint32_t B11 = 1 << 11;
static constexpr uint32_t B12 = 1 << 12;
static constexpr uint32_t B16 = 1 << 16;
static constexpr uint32_t B17 = 1 << 17;
static constexpr uint32_t B18 = 1 << 18;
static constexpr uint32_t B19 = 1 << 19;
static constexpr uint32_t B20 = 1 << 20;
static constexpr uint32_t B21 = 1 << 21;
static constexpr uint32_t B22 = 1 << 22;
static constexpr uint32_t B23 = 1 << 23;
static constexpr uint32_t B24 = 1 << 24;
static constexpr uint32_t B25 = 1 << 25;
static constexpr uint32_t B26 = 1 << 26;
static constexpr uint32_t B27 = 1 << 27;
// Constants used for the decoding or encoding of the individual fields of
// instructions. Based on section A5.1 from the "ARM Architecture Reference
// Manual, ARMv7-A and ARMv7-R edition". See:
// http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0406c
static constexpr uint32_t kConditionShift = 28;
static constexpr uint32_t kConditionBits = 4;
static constexpr uint32_t kTypeShift = 25;
static constexpr uint32_t kTypeBits = 3;
static constexpr uint32_t kLinkShift = 24;
static constexpr uint32_t kLinkBits = 1;
static constexpr uint32_t kUShift = 23;
static constexpr uint32_t kUBits = 1;
static constexpr uint32_t kOpcodeShift = 21;
static constexpr uint32_t kOpcodeBits = 4;
static constexpr uint32_t kSShift = 20;
static constexpr uint32_t kSBits = 1;
static constexpr uint32_t kRnShift = 16;
static constexpr uint32_t kRnBits = 4;
static constexpr uint32_t kRdShift = 12;
static constexpr uint32_t kRdBits = 4;
static constexpr uint32_t kRsShift = 8;
static constexpr uint32_t kRsBits = 4;
static constexpr uint32_t kRmShift = 0;
static constexpr uint32_t kRmBits = 4;
// Immediate instruction fields encoding.
static constexpr uint32_t kRotateShift = 8;
static constexpr uint32_t kRotateBits = 4;
static constexpr uint32_t kImmed8Shift = 0;
static constexpr uint32_t kImmed8Bits = 8;
// Shift instruction register fields encodings.
static constexpr uint32_t kShiftImmShift = 7;
static constexpr uint32_t kShiftRegisterShift = 8;
static constexpr uint32_t kShiftImmBits = 5;
static constexpr uint32_t kShiftShift = 5;
static constexpr uint32_t kShiftBits = 2;
// Load/store instruction offset field encoding.
static constexpr uint32_t kOffset12Shift = 0;
static constexpr uint32_t kOffset12Bits = 12;
static constexpr uint32_t kOffset12Mask = 0x00000fff;
// Mul instruction register field encodings.
static constexpr uint32_t kMulRdShift = 16;
static constexpr uint32_t kMulRdBits = 4;
static constexpr uint32_t kMulRnShift = 12;
static constexpr uint32_t kMulRnBits = 4;
// Div instruction register field encodings.
static constexpr uint32_t kDivRdShift = 16;
static constexpr uint32_t kDivRdBits = 4;
static constexpr uint32_t kDivRmShift = 8;
static constexpr uint32_t kDivRmBints = 4;
static constexpr uint32_t kDivRnShift = 0;
static constexpr uint32_t kDivRnBits = 4;
// ldrex/strex register field encodings.
static constexpr uint32_t kLdExRnShift = 16;
static constexpr uint32_t kLdExRtShift = 12;
static constexpr uint32_t kStrExRnShift = 16;
static constexpr uint32_t kStrExRdShift = 12;
static constexpr uint32_t kStrExRtShift = 0;
// MRC instruction offset field encoding.
static constexpr uint32_t kCRmShift = 0;
static constexpr uint32_t kCRmBits = 4;
static constexpr uint32_t kOpc2Shift = 5;
static constexpr uint32_t kOpc2Bits = 3;
static constexpr uint32_t kCoprocShift = 8;
static constexpr uint32_t kCoprocBits = 4;
static constexpr uint32_t kCRnShift = 16;
static constexpr uint32_t kCRnBits = 4;
static constexpr uint32_t kOpc1Shift = 21;
static constexpr uint32_t kOpc1Bits = 3;
static constexpr uint32_t kBranchOffsetMask = 0x00ffffff;
// A vector of pool-allocated x86 labels for CFG nodes.
using LabelVector = std::vector<Label *>;
LabelVector CfgNodeLabels;
Label *getOrCreateLabel(SizeT Number, LabelVector &Labels);
Label *getOrCreateCfgNodeLabel(SizeT NodeNumber) {
return getOrCreateLabel(NodeNumber, CfgNodeLabels);
}
void emitInt32(int32_t Value) { Buffer.emit<int32_t>(Value); }
static int32_t BkptEncoding(uint16_t imm16) {
// bkpt requires that the cond field is AL.
// cccc00010010iiiiiiiiiiii0111iiii where cccc=AL and i in imm16
return (CondARM32::AL << kConditionShift) | B24 | B21 |
((imm16 >> 4) << 8) | B6 | B5 | B4 | (imm16 & 0xf);
}
};
} // end of namespace ARM32
......
......@@ -35,8 +35,8 @@ class AssemblerMIPS32 : public Assembler {
AssemblerMIPS32 &operator=(const AssemblerMIPS32 &) = delete;
public:
explicit AssemblerMIPS32(bool use_far_branches = false)
: Assembler(Asm_MIPS32) {
explicit AssemblerMIPS32(GlobalContext *Ctx, bool use_far_branches = false)
: Assembler(Asm_MIPS32, Ctx) {
// This mode is only needed and implemented for MIPS32 and ARM.
assert(!use_far_branches);
(void)use_far_branches;
......
......@@ -45,8 +45,8 @@ class AssemblerX8632 : public X86Internal::AssemblerX86Base<TargetX8632> {
AssemblerX8632 &operator=(const AssemblerX8632 &) = delete;
public:
explicit AssemblerX8632(bool use_far_branches = false)
: X86Internal::AssemblerX86Base<TargetX8632>(Asm_X8632,
explicit AssemblerX8632(GlobalContext *Ctx, bool use_far_branches = false)
: X86Internal::AssemblerX86Base<TargetX8632>(Asm_X8632, Ctx,
use_far_branches) {}
~AssemblerX8632() override = default;
......
......@@ -45,8 +45,8 @@ class AssemblerX8664 : public X86Internal::AssemblerX86Base<TargetX8664> {
AssemblerX8664 &operator=(const AssemblerX8664 &) = delete;
public:
explicit AssemblerX8664(bool use_far_branches = false)
: X86Internal::AssemblerX86Base<TargetX8664>(Asm_X8664,
explicit AssemblerX8664(GlobalContext *Ctx, bool use_far_branches = false)
: X86Internal::AssemblerX86Base<TargetX8664>(Asm_X8664, Ctx,
use_far_branches) {}
~AssemblerX8664() override = default;
......
......@@ -115,8 +115,9 @@ template <class Machine> class AssemblerX86Base : public Assembler {
AssemblerX86Base &operator=(const AssemblerX86Base &) = delete;
protected:
AssemblerX86Base(AssemblerKind Kind, bool use_far_branches)
: Assembler(Kind) {
AssemblerX86Base(AssemblerKind Kind, GlobalContext *Ctx,
bool use_far_branches)
: Assembler(Kind, Ctx) {
// This mode is only needed and implemented for MIPS and ARM.
assert(!use_far_branches);
(void)use_far_branches;
......
......@@ -519,7 +519,7 @@ void GlobalContext::emitItems() {
case FT_Iasm: {
OstreamLocker L(this);
Cfg::emitTextHeader(MangledName, this, Asm.get());
Asm->emitIASBytes(this);
Asm->emitIASBytes();
} break;
case FT_Asm:
llvm::report_fatal_error("Unexpected FT_Asm");
......
......@@ -969,8 +969,8 @@ void InstARM32Ret::emit(const Cfg *Func) const {
}
void InstARM32Ret::emitIAS(const Cfg *Func) const {
(void)Func;
llvm_unreachable("Not yet implemented");
ARM32::AssemblerARM32 *Asm = Func->getAssembler<ARM32::AssemblerARM32>();
Asm->bx(RegARM32::Encoded_Reg_lr);
}
void InstARM32Ret::dump(const Cfg *Func) const {
......
......@@ -113,7 +113,7 @@ std::unique_ptr<Assembler> TargetLowering::createAssembler(TargetArch Target,
Cfg *Func) {
#define SUBZERO_TARGET(X) \
if (Target == Target_##X) \
return std::unique_ptr<Assembler>(new X::Assembler##X());
return std::unique_ptr<Assembler>(new X::Assembler##X(Func->getContext()));
#include "llvm/Config/SZTargets.def"
Func->setError("Unsupported target assembler");
......
......@@ -28,6 +28,18 @@
namespace Ice {
// UnimplementedError is defined as a macro so that we can get actual line
// numbers.
#define UnimplementedError(Flags) \
do { \
if (!static_cast<const ClFlags &>(Flags).getSkipUnimplemented()) { \
/* Use llvm_unreachable instead of report_fatal_error, which gives \
better stack traces. */ \
llvm_unreachable("Not yet implemented"); \
abort(); \
} \
} while (0)
/// LoweringContext makes it easy to iterate through non-deleted instructions in
/// a node, and insert new (lowered) instructions at the current point. Along
/// with the instruction list container and associated iterators, it holds the
......
......@@ -37,18 +37,6 @@ namespace Ice {
namespace {
// UnimplementedError is defined as a macro so that we can get actual line
// numbers.
#define UnimplementedError(Flags) \
do { \
if (!static_cast<const ClFlags &>(Flags).getSkipUnimplemented()) { \
/* Use llvm_unreachable instead of report_fatal_error, which gives \
better stack traces. */ \
llvm_unreachable("Not yet implemented"); \
abort(); \
} \
} while (0)
// The following table summarizes the logic for lowering the icmp instruction
// for i32 and narrower types. Each icmp condition has a clear mapping to an
// ARM32 conditional move instruction.
......@@ -3871,16 +3859,13 @@ void TargetDataARM32::lowerConstants() {
case FT_Elf:
UnimplementedError(Ctx->getFlags());
break;
case FT_Asm: {
case FT_Asm:
case FT_Iasm: {
OstreamLocker L(Ctx);
emitConstantPool<float>(Ctx);
emitConstantPool<double>(Ctx);
break;
}
case FT_Iasm: {
UnimplementedError(Ctx->getFlags());
break;
}
}
}
......@@ -3895,7 +3880,7 @@ void TargetDataARM32::lowerJumpTables() {
// Already emitted from Cfg
break;
case FT_Iasm: {
UnimplementedError(Ctx->getFlags());
// TODO(kschimpf): Fill this in when we get more information.
break;
}
}
......
......@@ -31,17 +31,6 @@
namespace Ice {
namespace {
void UnimplementedError(const ClFlags &Flags) {
if (!Flags.getSkipUnimplemented()) {
// Use llvm_unreachable instead of report_fatal_error, which gives better
// stack traces.
llvm_unreachable("Not yet implemented");
abort();
}
}
} // end of anonymous namespace
TargetMIPS32::TargetMIPS32(Cfg *Func) : TargetLowering(Func) {
// TODO: Don't initialize IntegerRegisters and friends every time. Instead,
// initialize in some sort of static initializer for the class.
......
; Shows that the ARM integrated assembler can translate a trivial,
; bundle-aligned function.
; RUN: %p2i --filetype=asm -i %s --target=arm32 \
; RUN: | FileCheck %s --check-prefix=ASM
; RUN: %p2i --filetype=iasm -i %s --target=arm32 \
; RUN: | FileCheck %s --check-prefix=IASM
define internal void @f() {
ret void
}
; ASM-LABEL:f:
; ASM-NEXT: .Lf$__0:
; ASM-NEXT: bx lr
; IASM-LABEL:f:
; IASM-NEXT: .byte 0x1e
; IASM-NEXT: .byte 0xff
; IASM-NEXT: .byte 0x2f
; IASM-NEXT: .byte 0xe1
; IASM-NEXT: .byte 0x70
; IASM-NEXT: .byte 0x0
; IASM-NEXT: .byte 0x20
; IASM-NEXT: .byte 0xe1
; IASM-NEXT: .byte 0x70
; IASM-NEXT: .byte 0x0
; IASM-NEXT: .byte 0x20
; IASM-NEXT: .byte 0xe1
; IASM-NEXT: .byte 0x70
; IASM-NEXT: .byte 0x0
; IASM-NEXT: .byte 0x20
; IASM-NEXT: .byte 0xe1
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment