diff --git a/cranelift/codegen/src/isa/riscv64/inst.isle b/cranelift/codegen/src/isa/riscv64/inst.isle index 16545b74716b..2cf04b6032c0 100644 --- a/cranelift/codegen/src/isa/riscv64/inst.isle +++ b/cranelift/codegen/src/isa/riscv64/inst.isle @@ -728,6 +728,7 @@ (CSub) (CAddw) (CSubw) + (CMul) )) ;; Opcodes for the CJ compressed instruction format @@ -781,6 +782,29 @@ (CFld) )) +;; Opcodes for the CSZN compressed instruction format +(type CsznOp (enum + (CNot) + (CZextb) + (CZexth) + (CZextw) + (CSextb) + (CSexth) +)) + +;; This is a mix of all Zcb memory adressing instructions +;; +;; Technically they are split across 4 different formats. +;; But they are all very similar, so we just group them all together. +(type ZcbMemOp (enum + (CLbu) + (CLhu) + (CLh) + (CSb) + (CSh) +)) + + (type CsrRegOP (enum ;; Atomic Read/Write CSR (CsrRW) diff --git a/cranelift/codegen/src/isa/riscv64/inst/args.rs b/cranelift/codegen/src/isa/riscv64/inst/args.rs index c9f8201f0249..a5acb54d0066 100644 --- a/cranelift/codegen/src/isa/riscv64/inst/args.rs +++ b/cranelift/codegen/src/isa/riscv64/inst/args.rs @@ -8,7 +8,7 @@ use crate::ir::condcodes::CondCode; use crate::isa::riscv64::inst::{reg_name, reg_to_gpr_num}; use crate::isa::riscv64::lower::isle::generated_code::{ - COpcodeSpace, CaOp, CbOp, CiOp, CiwOp, CjOp, ClOp, CrOp, CsOp, CssOp, + COpcodeSpace, CaOp, CbOp, CiOp, CiwOp, CjOp, ClOp, CrOp, CsOp, CssOp, CsznOp, ZcbMemOp, }; use crate::machinst::isle::WritableReg; @@ -1923,6 +1923,7 @@ impl CaOp { CaOp::CSub => 0b00, CaOp::CAddw => 0b01, CaOp::CSubw => 0b00, + CaOp::CMul => 0b10, } } @@ -1930,16 +1931,20 @@ impl CaOp { // https://github.com/michaeljclark/riscv-meta/blob/master/opcodes match self { CaOp::CAnd | CaOp::COr | CaOp::CXor | CaOp::CSub => 0b100_011, - CaOp::CSubw | CaOp::CAddw => 0b100_111, + CaOp::CSubw | CaOp::CAddw | CaOp::CMul => 0b100_111, } } pub fn op(&self) -> COpcodeSpace { // https://five-embeddev.com/riscv-isa-manual/latest/rvc-opcode-map.html#rvcopcodemap match self { - CaOp::CAnd | CaOp::COr | CaOp::CXor | CaOp::CSub | CaOp::CAddw | CaOp::CSubw => { - COpcodeSpace::C1 - } + CaOp::CAnd + | CaOp::COr + | CaOp::CXor + | CaOp::CSub + | CaOp::CAddw + | CaOp::CSubw + | CaOp::CMul => COpcodeSpace::C1, } } } @@ -2076,3 +2081,70 @@ impl ClOp { } } } + +impl CsznOp { + pub fn funct6(&self) -> u32 { + // https://github.com/michaeljclark/riscv-meta/blob/master/opcodes + match self { + CsznOp::CNot + | CsznOp::CZextw + | CsznOp::CZextb + | CsznOp::CZexth + | CsznOp::CSextb + | CsznOp::CSexth => 0b100_111, + } + } + + pub fn funct5(&self) -> u32 { + // https://github.com/michaeljclark/riscv-meta/blob/master/opcodes + match self { + CsznOp::CNot => 0b11_101, + CsznOp::CZextb => 0b11_000, + CsznOp::CZexth => 0b11_010, + CsznOp::CZextw => 0b11_100, + CsznOp::CSextb => 0b11_001, + CsznOp::CSexth => 0b11_011, + } + } + + pub fn op(&self) -> COpcodeSpace { + // https://five-embeddev.com/riscv-isa-manual/latest/rvc-opcode-map.html#rvcopcodemap + match self { + CsznOp::CNot + | CsznOp::CZextb + | CsznOp::CZexth + | CsznOp::CZextw + | CsznOp::CSextb + | CsznOp::CSexth => COpcodeSpace::C1, + } + } +} + +impl ZcbMemOp { + pub fn funct6(&self) -> u32 { + // https://github.com/michaeljclark/riscv-meta/blob/master/opcodes + match self { + ZcbMemOp::CLbu => 0b100_000, + // These two opcodes are differentiated in the imm field of the instruction. + ZcbMemOp::CLhu | ZcbMemOp::CLh => 0b100_001, + ZcbMemOp::CSb => 0b100_010, + ZcbMemOp::CSh => 0b100_011, + } + } + + pub fn imm_bits(&self) -> u8 { + match self { + ZcbMemOp::CLhu | ZcbMemOp::CLh | ZcbMemOp::CSh => 1, + ZcbMemOp::CLbu | ZcbMemOp::CSb => 2, + } + } + + pub fn op(&self) -> COpcodeSpace { + // https://five-embeddev.com/riscv-isa-manual/latest/rvc-opcode-map.html#rvcopcodemap + match self { + ZcbMemOp::CLbu | ZcbMemOp::CLhu | ZcbMemOp::CLh | ZcbMemOp::CSb | ZcbMemOp::CSh => { + COpcodeSpace::C0 + } + } + } +} diff --git a/cranelift/codegen/src/isa/riscv64/inst/emit.rs b/cranelift/codegen/src/isa/riscv64/inst/emit.rs index 25b0419684bc..d6cf12d6066b 100644 --- a/cranelift/codegen/src/isa/riscv64/inst/emit.rs +++ b/cranelift/codegen/src/isa/riscv64/inst/emit.rs @@ -4,7 +4,7 @@ use crate::binemit::StackMap; use crate::ir::{self, LibCall, RelSourceLoc, TrapCode}; use crate::isa::riscv64::inst::*; use crate::isa::riscv64::lower::isle::generated_code::{ - CaOp, CbOp, CiOp, CiwOp, ClOp, CrOp, CsOp, CssOp, + CaOp, CbOp, CiOp, CiwOp, ClOp, CrOp, CsOp, CssOp, CsznOp, ZcbMemOp, }; use crate::machinst::{AllocationConsumer, Reg, Writable}; use crate::trace; @@ -465,7 +465,11 @@ impl Inst { state: &mut EmitState, start_off: &mut u32, ) -> Option<()> { + let has_m = emit_info.isa_flags.has_m(); + let has_zba = emit_info.isa_flags.has_zba(); + let has_zbb = emit_info.isa_flags.has_zbb(); let has_zca = emit_info.isa_flags.has_zca(); + let has_zcb = emit_info.isa_flags.has_zcb(); let has_zcd = emit_info.isa_flags.has_zcd(); // Currently all compressed extensions (Zcb, Zcd, Zcmp, Zcmt, etc..) require Zca @@ -513,7 +517,8 @@ impl Inst { | AluOPRRR::Xor | AluOPRRR::Sub | AluOPRRR::Addw - | AluOPRRR::Subw), + | AluOPRRR::Subw + | AluOPRRR::Mul), rd, rs1, rs2, @@ -525,7 +530,8 @@ impl Inst { AluOPRRR::Sub => CaOp::CSub, AluOPRRR::Addw => CaOp::CAddw, AluOPRRR::Subw => CaOp::CSubw, - _ => unreachable!(), + AluOPRRR::Mul if has_zcb && has_m => CaOp::CMul, + _ => return None, }; sink.put2(encode_ca_type(op, rd, rs2)); @@ -700,6 +706,22 @@ impl Inst { sink.put2(encode_cb_type(op, rd, imm6)); } + // c.zextb + // + // This is an alias for `andi rd, rd, 0xff` + Inst::AluRRImm12 { + alu_op: AluOPRRI::Andi, + rd, + rs, + imm12, + } if has_zcb + && rd.to_reg() == rs + && reg_is_compressible(rs) + && imm12.as_i16() == 0xff => + { + sink.put2(encode_cszn_type(CsznOp::CZextb, rd)); + } + // c.andi Inst::AluRRImm12 { alu_op: AluOPRRI::Andi, @@ -752,7 +774,9 @@ impl Inst { // Regular Loads Inst::Load { rd, - op: op @ (LoadOP::Lw | LoadOP::Ld | LoadOP::Fld), + op: + op + @ (LoadOP::Lw | LoadOP::Ld | LoadOP::Fld | LoadOP::Lbu | LoadOP::Lhu | LoadOP::Lh), from, flags, } if reg_is_compressible(rd.to_reg()) @@ -766,17 +790,49 @@ impl Inst { // We encode the offset in multiples of the store size. let offset = from.get_offset_with_state(state); - let imm5 = u8::try_from(offset / op.size()) - .ok() - .and_then(Uimm5::maybe_from_u8)?; + let offset = u8::try_from(offset / op.size()).ok()?; - // Floating point loads are not included in the base Zca extension - // but in a separate Zcd extension. Both of these are part of the C Extension. - let op = match op { - LoadOP::Lw => ClOp::CLw, - LoadOP::Ld => ClOp::CLd, - LoadOP::Fld if has_zcd => ClOp::CFld, - _ => return None, + // We mix two different formats here. + // + // c.lw / c.ld / c.fld instructions are available in the standard Zca + // extension using the CL format. + // + // c.lbu / c.lhu / c.lh are only available in the Zcb extension and + // are also encoded differently. Technically they each have a different + // format, but they are similar enough that we can group them. + let is_zcb_load = matches!(op, LoadOP::Lbu | LoadOP::Lhu | LoadOP::Lh); + let encoded = if is_zcb_load { + if !has_zcb { + return None; + } + + let op = match op { + LoadOP::Lbu => ZcbMemOp::CLbu, + LoadOP::Lhu => ZcbMemOp::CLhu, + LoadOP::Lh => ZcbMemOp::CLh, + _ => unreachable!(), + }; + + // Byte stores & loads have 2 bits of immediate offset. Halfword stores + // and loads only have 1 bit. + let imm2 = Uimm2::maybe_from_u8(offset)?; + if (offset & !((1 << op.imm_bits()) - 1)) != 0 { + return None; + } + + encode_zcbmem_load(op, rd, base, imm2) + } else { + // Floating point loads are not included in the base Zca extension + // but in a separate Zcd extension. Both of these are part of the C Extension. + let op = match op { + LoadOP::Lw => ClOp::CLw, + LoadOP::Ld => ClOp::CLd, + LoadOP::Fld if has_zcd => ClOp::CFld, + _ => return None, + }; + let imm5 = Uimm5::maybe_from_u8(offset)?; + + encode_cl_type(op, rd, base, imm5) }; let srcloc = state.cur_srcloc(); @@ -784,7 +840,7 @@ impl Inst { // Register the offset at which the actual load instruction starts. sink.add_trap(TrapCode::HeapOutOfBounds); } - sink.put2(encode_cl_type(op, rd, base, imm5)); + sink.put2(encoded); } // Stack Based Stores @@ -822,7 +878,7 @@ impl Inst { // Regular Stores Inst::Store { src, - op: op @ (StoreOP::Sw | StoreOP::Sd | StoreOP::Fsd), + op: op @ (StoreOP::Sw | StoreOP::Sd | StoreOP::Fsd | StoreOP::Sh | StoreOP::Sb), to, flags, } if reg_is_compressible(src) @@ -836,17 +892,47 @@ impl Inst { // We encode the offset in multiples of the store size. let offset = to.get_offset_with_state(state); - let imm5 = u8::try_from(offset / op.size()) - .ok() - .and_then(Uimm5::maybe_from_u8)?; + let offset = u8::try_from(offset / op.size()).ok()?; - // Floating point stores are not included in the base Zca extension - // but in a separate Zcd extension. Both of these are part of the C Extension. - let op = match op { - StoreOP::Sw => CsOp::CSw, - StoreOP::Sd => CsOp::CSd, - StoreOP::Fsd if has_zcd => CsOp::CFsd, - _ => return None, + // We mix two different formats here. + // + // c.sw / c.sd / c.fsd instructions are available in the standard Zca + // extension using the CL format. + // + // c.sb / c.sh are only available in the Zcb extension and are also + // encoded differently. + let is_zcb_store = matches!(op, StoreOP::Sh | StoreOP::Sb); + let encoded = if is_zcb_store { + if !has_zcb { + return None; + } + + let op = match op { + StoreOP::Sh => ZcbMemOp::CSh, + StoreOP::Sb => ZcbMemOp::CSb, + _ => unreachable!(), + }; + + // Byte stores & loads have 2 bits of immediate offset. Halfword stores + // and loads only have 1 bit. + let imm2 = Uimm2::maybe_from_u8(offset)?; + if (offset & !((1 << op.imm_bits()) - 1)) != 0 { + return None; + } + + encode_zcbmem_store(op, src, base, imm2) + } else { + // Floating point stores are not included in the base Zca extension + // but in a separate Zcd extension. Both of these are part of the C Extension. + let op = match op { + StoreOP::Sw => CsOp::CSw, + StoreOP::Sd => CsOp::CSd, + StoreOP::Fsd if has_zcd => CsOp::CFsd, + _ => return None, + }; + let imm5 = Uimm5::maybe_from_u8(offset)?; + + encode_cs_type(op, src, base, imm5) }; let srcloc = state.cur_srcloc(); @@ -854,7 +940,64 @@ impl Inst { // Register the offset at which the actual load instruction starts. sink.add_trap(TrapCode::HeapOutOfBounds); } - sink.put2(encode_cs_type(op, src, base, imm5)); + sink.put2(encoded); + } + + // c.not + // + // This is an alias for `xori rd, rd, -1` + Inst::AluRRImm12 { + alu_op: AluOPRRI::Xori, + rd, + rs, + imm12, + } if has_zcb + && rd.to_reg() == rs + && reg_is_compressible(rs) + && imm12.as_i16() == -1 => + { + sink.put2(encode_cszn_type(CsznOp::CNot, rd)); + } + + // c.sext.b / c.sext.h / c.zext.h + // + // These are all the extend instructions present in `Zcb`, they + // also require `Zbb` since they aren't available in the base ISA. + Inst::AluRRImm12 { + alu_op: alu_op @ (AluOPRRI::Sextb | AluOPRRI::Sexth | AluOPRRI::Zexth), + rd, + rs, + imm12, + } if has_zcb + && has_zbb + && rd.to_reg() == rs + && reg_is_compressible(rs) + && imm12.as_i16() == 0 => + { + let op = match alu_op { + AluOPRRI::Sextb => CsznOp::CSextb, + AluOPRRI::Sexth => CsznOp::CSexth, + AluOPRRI::Zexth => CsznOp::CZexth, + _ => unreachable!(), + }; + sink.put2(encode_cszn_type(op, rd)); + } + + // c.zext.w + // + // This is an alias for `add.uw rd, rd, zero` + Inst::AluRRR { + alu_op: AluOPRRR::Adduw, + rd, + rs1, + rs2, + } if has_zcb + && has_zba + && rd.to_reg() == rs1 + && reg_is_compressible(rs1) + && rs2 == zero_reg() => + { + sink.put2(encode_cszn_type(CsznOp::CZextw, rd)); } _ => return None, diff --git a/cranelift/codegen/src/isa/riscv64/inst/encode.rs b/cranelift/codegen/src/isa/riscv64/inst/encode.rs index e3054bafb927..fae2f929c795 100644 --- a/cranelift/codegen/src/isa/riscv64/inst/encode.rs +++ b/cranelift/codegen/src/isa/riscv64/inst/encode.rs @@ -9,9 +9,9 @@ use super::*; use crate::isa::riscv64::inst::reg_to_gpr_num; use crate::isa::riscv64::lower::isle::generated_code::{ - COpcodeSpace, CaOp, CbOp, CiOp, CiwOp, CjOp, ClOp, CrOp, CsOp, CssOp, VecAluOpRImm5, + COpcodeSpace, CaOp, CbOp, CiOp, CiwOp, CjOp, ClOp, CrOp, CsOp, CssOp, CsznOp, VecAluOpRImm5, VecAluOpRR, VecAluOpRRImm5, VecAluOpRRR, VecAluOpRRRImm5, VecAluOpRRRR, VecElementWidth, - VecOpCategory, VecOpMasking, + VecOpCategory, VecOpMasking, ZcbMemOp, }; use crate::machinst::isle::WritableReg; use crate::Reg; @@ -604,3 +604,54 @@ fn encode_cs_cl_type_bits( bits |= unsigned_field_width(funct3, 3) << 13; bits.try_into().unwrap() } + +// Encode a CSZN type instruction. +// +// This is an additional encoding format that is introduced in the Zcb extension. +// +// 0--1-2---------6-7--------9-10------15 +// |op | funct5 | rd/rs1 | funct6 | +pub fn encode_cszn_type(op: CsznOp, rd: WritableReg) -> u16 { + let mut bits = 0; + bits |= unsigned_field_width(op.op().bits(), 2); + bits |= unsigned_field_width(op.funct5(), 5) << 2; + bits |= reg_to_compressed_gpr_num(rd.to_reg()) << 7; + bits |= unsigned_field_width(op.funct6(), 6) << 10; + bits.try_into().unwrap() +} + +// Encodes the various memory operations in the Zcb extension. +// +// 0--1-2----------4-5----------6-7---------9-10-------15 +// |op | dest/src | imm(2-bit) | base | funct6 | +fn encode_zcbmem_bits(op: ZcbMemOp, dest_src: Reg, base: Reg, imm: Uimm2) -> u16 { + let imm = imm.bits(); + + // For these ops, bit 6 is part of the opcode, and bit 5 encodes the imm offset. + let imm = match op { + ZcbMemOp::CLh | ZcbMemOp::CLhu | ZcbMemOp::CSh => { + debug_assert_eq!(imm & !1, 0); + // Only c.lh has this bit as 1 + let opcode_bit = (op == ZcbMemOp::CLh) as u8; + imm | (opcode_bit << 1) + } + // In the rest of the ops the imm is reversed. + _ => ((imm & 1) << 1) | ((imm >> 1) & 1), + }; + + let mut bits = 0; + bits |= unsigned_field_width(op.op().bits(), 2); + bits |= reg_to_compressed_gpr_num(dest_src) << 2; + bits |= unsigned_field_width(imm as u32, 2) << 5; + bits |= reg_to_compressed_gpr_num(base) << 7; + bits |= unsigned_field_width(op.funct6(), 6) << 10; + bits.try_into().unwrap() +} + +pub fn encode_zcbmem_load(op: ZcbMemOp, rd: WritableReg, base: Reg, imm: Uimm2) -> u16 { + encode_zcbmem_bits(op, rd.to_reg(), base, imm) +} + +pub fn encode_zcbmem_store(op: ZcbMemOp, src: Reg, base: Reg, imm: Uimm2) -> u16 { + encode_zcbmem_bits(op, src, base, imm) +} diff --git a/cranelift/codegen/src/isa/riscv64/inst/imms.rs b/cranelift/codegen/src/isa/riscv64/inst/imms.rs index a17edcdde291..8b6854186193 100644 --- a/cranelift/codegen/src/isa/riscv64/inst/imms.rs +++ b/cranelift/codegen/src/isa/riscv64/inst/imms.rs @@ -263,6 +263,34 @@ impl Display for Uimm5 { } } +/// A unsigned 2-bit immediate. +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct Uimm2 { + value: u8, +} + +impl Uimm2 { + /// Create an unsigned 2-bit immediate from an u8 + pub fn maybe_from_u8(value: u8) -> Option { + if value <= 3 { + Some(Self { value }) + } else { + None + } + } + + /// Bits for encoding. + pub fn bits(&self) -> u8 { + self.value & 0x3 + } +} + +impl Display for Uimm2 { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + write!(f, "{}", self.value) + } +} + impl Inst { pub(crate) fn imm_min() -> i64 { let imm20_max: i64 = (1 << 19) << 12; diff --git a/cranelift/filetests/filetests/isa/riscv64/zcb.clif b/cranelift/filetests/filetests/isa/riscv64/zcb.clif new file mode 100644 index 000000000000..76c68310c209 --- /dev/null +++ b/cranelift/filetests/filetests/isa/riscv64/zcb.clif @@ -0,0 +1,239 @@ +test compile precise-output +set unwind_info=false +target riscv64 has_zca has_zcb has_zbb has_zba + +function %c_mul(i64, i64) -> i64 { +block0(v0: i64, v1: i64): + v2 = imul.i64 v0, v1 + return v2 +} + +; VCode: +; block0: +; mul a0,a0,a1 +; ret +; +; Disassembled: +; block0: ; offset 0x0 +; .byte 0x4d, 0x9d +; c.jr ra + + + +function %c_not(i64) -> i64 { +block0(v0: i64): + v1 = bnot.i64 v0 + return v1 +} + +; VCode: +; block0: +; not a0,a0 +; ret +; +; Disassembled: +; block0: ; offset 0x0 +; .byte 0x75, 0x9d +; c.jr ra + +function %c_zextb(i8) -> i64 { +block0(v0: i8): + v1 = uextend.i64 v0 + return v1 +} + +; VCode: +; block0: +; andi a0,a0,255 +; ret +; +; Disassembled: +; block0: ; offset 0x0 +; .byte 0x61, 0x9d +; c.jr ra + +function %c_zexth(i16) -> i64 { +block0(v0: i16): + v1 = uextend.i64 v0 + return v1 +} + +; VCode: +; block0: +; zext.h a0,a0 +; ret +; +; Disassembled: +; block0: ; offset 0x0 +; .byte 0x69, 0x9d +; c.jr ra + +function %c_zextw(i32) -> i64 { +block0(v0: i32): + v1 = uextend.i64 v0 + return v1 +} + +; VCode: +; block0: +; zext.w a0,a0 +; ret +; +; Disassembled: +; block0: ; offset 0x0 +; .byte 0x71, 0x9d +; c.jr ra + +function %c_sextb(i8) -> i64 { +block0(v0: i8): + v1 = sextend.i64 v0 + return v1 +} + +; VCode: +; block0: +; sext.b a0,a0 +; ret +; +; Disassembled: +; block0: ; offset 0x0 +; .byte 0x65, 0x9d +; c.jr ra + +function %c_sexth(i16) -> i64 { +block0(v0: i16): + v1 = sextend.i64 v0 + return v1 +} + +; VCode: +; block0: +; sext.h a0,a0 +; ret +; +; Disassembled: +; block0: ; offset 0x0 +; .byte 0x6d, 0x9d +; c.jr ra + + +function %c_lbu(i64) -> i16, i64 { +block0(v0: i64): + v1 = uload8.i16 v0+0 + v2 = uload8.i64 v0+3 + return v1, v2 +} + +; VCode: +; block0: +; lbu a3,0(a0) +; mv a4,a3 +; lbu a1,3(a0) +; mv a0,a4 +; ret +; +; Disassembled: +; block0: ; offset 0x0 +; .byte 0x14, 0x81 +; c.mv a4, a3 +; .byte 0x6c, 0x81 +; c.mv a0, a4 +; c.jr ra + +function %c_lhu(i64) -> i32, i64 { +block0(v0: i64): + v1 = uload16.i32 v0+0 + v2 = uload16.i64 v0+2 + return v1, v2 +} + +; VCode: +; block0: +; lhu a3,0(a0) +; mv a4,a3 +; lhu a1,2(a0) +; mv a0,a4 +; ret +; +; Disassembled: +; block0: ; offset 0x0 +; .byte 0x14, 0x85 +; c.mv a4, a3 +; .byte 0x2c, 0x85 +; c.mv a0, a4 +; c.jr ra + +function %c_lh(i64) -> i16, i16 { +block0(v0: i64): + v1 = load.i16 v0+0 + v2 = load.i16 v0+2 + return v1, v2 +} + +; VCode: +; block0: +; lh a3,0(a0) +; mv a4,a3 +; lh a1,2(a0) +; mv a0,a4 +; ret +; +; Disassembled: +; block0: ; offset 0x0 +; .byte 0x54, 0x85 +; c.mv a4, a3 +; .byte 0x6c, 0x85 +; c.mv a0, a4 +; c.jr ra + + +function %c_sb(i64, i8) { +block0(v0: i64, v1: i8): + store.i8 v1, v0+0 + store.i8 v1, v0+1 + store.i8 v1, v0+2 + store.i8 v1, v0+3 + store.i8 v1, v0+4 + return +} + +; VCode: +; block0: +; sb a1,0(a0) +; sb a1,1(a0) +; sb a1,2(a0) +; sb a1,3(a0) +; sb a1,4(a0) +; ret +; +; Disassembled: +; block0: ; offset 0x0 +; .byte 0x0c, 0x89 +; .byte 0x4c, 0x89 +; .byte 0x2c, 0x89 +; .byte 0x6c, 0x89 +; sb a1, 4(a0) +; c.jr ra + +function %c_sh(i64, i16) { +block0(v0: i64, v1: i16): + store.i16 v1, v0+0 + store.i16 v1, v0+2 + store.i16 v1, v0+3 + return +} + +; VCode: +; block0: +; sh a1,0(a0) +; sh a1,2(a0) +; sh a1,3(a0) +; ret +; +; Disassembled: +; block0: ; offset 0x0 +; .byte 0x0c, 0x8d +; .byte 0x2c, 0x8d +; sh a1, 3(a0) +; c.jr ra +