From 8870c38fc7e3d3bcca9e76308649a32520255886 Mon Sep 17 00:00:00 2001 From: luchak Date: Mon, 23 Jan 2023 21:52:14 -0800 Subject: [PATCH 01/11] Add v128 type and SIMD intrinsics (minus shuffle) --- src/ast.rs | 11 +- src/constfold.rs | 3 +- src/emit.rs | 63 ++++++++- src/intrinsics.rs | 338 ++++++++++++++++++++++++++++++++++++++++++++++ src/parser.rs | 12 +- src/typecheck.rs | 84 ++++++++++++ 6 files changed, 503 insertions(+), 8 deletions(-) diff --git a/src/ast.rs b/src/ast.rs index 309e2c1..5ea2b98 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -243,6 +243,7 @@ pub enum Expr { I64Const(i64), F32Const(f32), F64Const(f64), + V128Const(i128), Variable { name: String, local_id: Option, @@ -382,15 +383,17 @@ pub enum Type { I64, F32, F64, + V128 } impl fmt::Display for Type { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - Type::I32 => write!(f, "i32"), - Type::I64 => write!(f, "i64"), - Type::F32 => write!(f, "f32"), - Type::F64 => write!(f, "f64"), + Type::I32 => write!(f, "i32"), + Type::I64 => write!(f, "i64"), + Type::F32 => write!(f, "f32"), + Type::F64 => write!(f, "f64"), + Type::V128 => write!(f, "v128"), } } } diff --git a/src/constfold.rs b/src/constfold.rs index ab44801..3e0416f 100644 --- a/src/constfold.rs +++ b/src/constfold.rs @@ -319,7 +319,8 @@ fn fold_expr(context: &Context, expr: &mut ast::Expression) { ast::Expr::I32Const(_) | ast::Expr::I64Const(_) | ast::Expr::F32Const(_) - | ast::Expr::F64Const(_) => (), + | ast::Expr::F64Const(_) + | ast::Expr::V128Const(_) => (), ast::Expr::Variable { ref name, .. } => { if let Some(value) = context.consts.get(name) { expr.expr = value.clone(); diff --git a/src/emit.rs b/src/emit.rs index f6d6b3b..15c6598 100644 --- a/src/emit.rs +++ b/src/emit.rs @@ -8,7 +8,7 @@ use wasm_encoder::{ use crate::{ ast, - intrinsics::{Intrinsics, MemInstruction}, + intrinsics::{Intrinsics, MemInstruction, MemLaneInstruction, LaneInstruction}, Options, }; @@ -279,6 +279,7 @@ fn const_instr(expr: &ast::Expression) -> Instruction { ast::Expr::F32Const(v) => Instruction::F32Const(v), ast::Expr::I64Const(v) => Instruction::I64Const(v), ast::Expr::F64Const(v) => Instruction::F64Const(v), + ast::Expr::V128Const(v) => Instruction::V128Const(v), _ => unreachable!(), } } @@ -437,6 +438,7 @@ fn emit_expression<'a>(ctx: &mut FunctionContext<'a>, expr: &'a ast::Expression) emit_expression(ctx, value); ctx.function.instruction(&Instruction::F64Neg); } + (V128, Negate) => unreachable!(), (I32, Not) => { emit_expression(ctx, value); ctx.function.instruction(&Instruction::I32Eqz); @@ -533,6 +535,8 @@ fn emit_expression<'a>(ctx: &mut FunctionContext<'a>, expr: &'a ast::Expression) (F64, Le) => Instruction::F64Le, (F64, Gt) => Instruction::F64Gt, (F64, Ge) => Instruction::F64Ge, + + (V128, _) => unreachable!(), }); } ast::Expr::Branch(label) => { @@ -572,6 +576,9 @@ fn emit_expression<'a>(ctx: &mut FunctionContext<'a>, expr: &'a ast::Expression) ast::Expr::F64Const(v) => { ctx.function.instruction(&Instruction::F64Const(*v)); } + ast::Expr::V128Const(v) => { + ctx.function.instruction(&Instruction::V128Const(*v)); + } ast::Expr::Assign { name, value, @@ -657,7 +664,8 @@ fn emit_expression<'a>(ctx: &mut FunctionContext<'a>, expr: &'a ast::Expression) (F32, F64) => Some(Instruction::F64PromoteF32), (F64, F32) => Some(Instruction::F32DemoteF64), - (I32, I32) | (I64, I64) | (F32, F32) | (F64, F64) => None, + (I32, I32) | (I64, I64) | (F32, F32) | (F64, F64) | (V128, V128) => None, + (V128, _) | (_, V128) => unreachable!(), }; if let Some(inst) = inst { ctx.function.instruction(&inst); @@ -679,15 +687,65 @@ fn emit_expression<'a>(ctx: &mut FunctionContext<'a>, expr: &'a ast::Expression) memory_index: 0, }) } + fn mem_lane_instruction( + inst: MemLaneInstruction, + lane: u8, + params: &[ast::Expression], + ) -> Instruction<'static> { + let offset = params + .get(0) + .map(|e| e.const_i32() as u32 as u64) + .unwrap_or(0); + let alignment = params.get(1).map(|e| e.const_i32() as u32); + (inst.instruction)(MemArg { + offset, + align: alignment.unwrap_or(inst.natural_alignment), + memory_index: 0, + }, lane) + } + fn lane_instruction( + inst: LaneInstruction, + lane: u8 + ) -> Instruction<'static> { + (inst.instruction)(lane) + } if let Some(load) = ctx.intrinsics.find_load(name) { emit_expression(ctx, ¶ms[0]); ctx.function .instruction(&mem_instruction(load, ¶ms[1..])); + } else if let Some(load_lane) = ctx.intrinsics.find_load_lane(name) { + let lane = params + .get(0) + .map(|e| e.const_i32() as u8) + .unwrap(); + emit_expression(ctx, ¶ms[1]); + ctx.function + .instruction(&mem_lane_instruction(load_lane, lane, ¶ms[2..])); } else if let Some(store) = ctx.intrinsics.find_store(name) { emit_expression(ctx, ¶ms[1]); emit_expression(ctx, ¶ms[0]); ctx.function .instruction(&mem_instruction(store, ¶ms[2..])); + } else if let Some(store_lane) = ctx.intrinsics.find_store_lane(name) { + emit_expression(ctx, ¶ms[2]); + emit_expression(ctx, ¶ms[0]); + let lane = params + .get(1) + .map(|e| e.const_i32() as u8) + .unwrap(); + ctx.function + .instruction(&mem_lane_instruction(store_lane, lane, ¶ms[3..])); + } else if let Some(lane_instr) = ctx.intrinsics.find_lane(name) { + emit_expression(ctx, ¶ms[0]); + let lane = params + .get(1) + .map(|e| e.const_i32() as u8) + .unwrap(); + if let Some(_) = lane_instr.param_type { + emit_expression(ctx, ¶ms[2]); + } + ctx.function + .instruction(&lane_instruction(lane_instr, lane)); } else { for param in params { emit_expression(ctx, param); @@ -762,6 +820,7 @@ fn map_type(t: ast::Type) -> ValType { ast::Type::I64 => ValType::I64, ast::Type::F32 => ValType::F32, ast::Type::F64 => ValType::F64, + ast::Type::V128 => ValType::V128, } } diff --git a/src/intrinsics.rs b/src/intrinsics.rs index f4f2d4d..e9d977b 100644 --- a/src/intrinsics.rs +++ b/src/intrinsics.rs @@ -1,5 +1,6 @@ use crate::ast::Type; use enc::MemArg; +use enc::Lane; use std::collections::HashMap; use wasm_encoder as enc; @@ -132,6 +133,240 @@ impl Intrinsics { self.inst("i64.trunc_sat_f64_s", &[F64], Some(I64), I::I64TruncSatF64S); self.inst("i64.trunc_sat_f64_u", &[F64], Some(I64), I::I64TruncSatF64U); + self.inst("i8x16.splat", &[I32], Some(V128), I::I8x16Splat); + self.inst("i16x8.splat", &[I32], Some(V128), I::I16x8Splat); + self.inst("i32x4.splat", &[I32], Some(V128), I::I32x4Splat); + self.inst("i64x2.splat", &[I64], Some(V128), I::I64x2Splat); + self.inst("f32x4.splat", &[F32], Some(V128), I::F32x4Splat); + self.inst("f64x2.splat", &[F64], Some(V128), I::F64x2Splat); + + // skipped: i8x16.shuffle (requires special handling for 16 literal lane values) + self.inst("i8x16.swizzle", &[V128, V128], Some(V128), I::I8x16Swizzle); + + self.inst("i8x16.add", &[V128, V128], Some(V128), I::I8x16Add); + self.inst("i16x8.add", &[V128, V128], Some(V128), I::I16x8Add); + self.inst("i32x4.add", &[V128, V128], Some(V128), I::I32x4Add); + self.inst("i64x2.add", &[V128, V128], Some(V128), I::I64x2Add); + self.inst("f32x4.add", &[V128, V128], Some(V128), I::F32x4Add); + self.inst("f64x2.add", &[V128, V128], Some(V128), I::F64x2Add); + + self.inst("i8x16.sub", &[V128, V128], Some(V128), I::I8x16Sub); + self.inst("i16x8.sub", &[V128, V128], Some(V128), I::I16x8Sub); + self.inst("i32x4.sub", &[V128, V128], Some(V128), I::I32x4Sub); + self.inst("i64x2.sub", &[V128, V128], Some(V128), I::I64x2Sub); + self.inst("f32x4.sub", &[V128, V128], Some(V128), I::F32x4Sub); + self.inst("f64x2.sub", &[V128, V128], Some(V128), I::F64x2Sub); + + self.inst("i16x8.mul", &[V128, V128], Some(V128), I::I16x8Mul); + self.inst("i32x4.mul", &[V128, V128], Some(V128), I::I32x4Mul); + self.inst("i64x2.mul", &[V128, V128], Some(V128), I::I64x2Mul); + self.inst("f32x4.mul", &[V128, V128], Some(V128), I::F32x4Mul); + self.inst("f64x2.mul", &[V128, V128], Some(V128), I::F64x2Mul); + + self.inst("i32x4.dot_i16x8_s", &[V128, V128], Some(V128), I::I32x4DotI16x8S); + + self.inst("i8x16.neg", &[V128, V128], Some(V128), I::I8x16Neg); + self.inst("i16x8.neg", &[V128, V128], Some(V128), I::I16x8Neg); + self.inst("i32x4.neg", &[V128, V128], Some(V128), I::I32x4Neg); + self.inst("i64x2.neg", &[V128, V128], Some(V128), I::I64x2Neg); + self.inst("f32x4.neg", &[V128, V128], Some(V128), I::F32x4Neg); + self.inst("f64x2.neg", &[V128, V128], Some(V128), I::F64x2Neg); + + self.inst("i16x8.extmul_low_i8x16_s", &[V128, V128], Some(V128), I::I16x8ExtMulLowI8x16S); + self.inst("i16x8.extmul_high_i8x16_s", &[V128, V128], Some(V128), I::I16x8ExtMulHighI8x16S); + self.inst("i16x8.extmul_low_i8x16_u", &[V128, V128], Some(V128), I::I16x8ExtMulLowI8x16U); + self.inst("i16x8.extmul_high_i8x16_u", &[V128, V128], Some(V128), I::I16x8ExtMulHighI8x16U); + self.inst("i32x4.extmul_low_i16x8_s", &[V128, V128], Some(V128), I::I32x4ExtMulLowI16x8S); + self.inst("i32x4.extmul_high_i16x8_s", &[V128, V128], Some(V128), I::I32x4ExtMulHighI16x8S); + self.inst("i32x4.extmul_low_i16x8_u", &[V128, V128], Some(V128), I::I32x4ExtMulLowI16x8U); + self.inst("i32x4.extmul_high_i16x8_u", &[V128, V128], Some(V128), I::I32x4ExtMulHighI16x8U); + self.inst("i64x2.extmul_low_i32x4_s", &[V128, V128], Some(V128), I::I64x2ExtMulLowI32x4S); + self.inst("i64x2.extmul_high_i32x4_s", &[V128, V128], Some(V128), I::I64x2ExtMulHighI32x4S); + self.inst("i64x2.extmul_low_i32x4_u", &[V128, V128], Some(V128), I::I64x2ExtMulLowI32x4U); + self.inst("i64x2.extmul_high_i32x4_u", &[V128, V128], Some(V128), I::I64x2ExtMulHighI32x4U); + + self.inst("i16x8.extadd_pairwise_i8x16_s", &[V128], Some(V128), I::I16x8ExtAddPairwiseI8x16S); + self.inst("i16x8.extadd_pairwise_i8x16_u", &[V128], Some(V128), I::I16x8ExtAddPairwiseI8x16U); + self.inst("i32x4.extadd_pairwise_i16x8_s", &[V128], Some(V128), I::I32x4ExtAddPairwiseI16x8S); + self.inst("i32x4.extadd_pairwise_i16x8_u", &[V128], Some(V128), I::I32x4ExtAddPairwiseI16x8U); + + self.inst("i8x16.add_sat_s", &[V128, V128], Some(V128), I::I16x8AddSatS); + self.inst("i8x16.add_sat_u", &[V128, V128], Some(V128), I::I16x8AddSatU); + self.inst("i16x8.add_sat_s", &[V128, V128], Some(V128), I::I16x8AddSatS); + self.inst("i16x8.add_sat_u", &[V128, V128], Some(V128), I::I16x8AddSatU); + + self.inst("i8x16.sub_sat_s", &[V128, V128], Some(V128), I::I16x8SubSatS); + self.inst("i8x16.sub_sat_u", &[V128, V128], Some(V128), I::I16x8SubSatU); + self.inst("i16x8.sub_sat_s", &[V128, V128], Some(V128), I::I16x8SubSatS); + self.inst("i16x8.sub_sat_u", &[V128, V128], Some(V128), I::I16x8SubSatU); + + self.inst("i16x8.q15mulr_sat_s", &[V128, V128], Some(V128), I::I16x8Q15MulrSatS); + + self.inst("i8x16.min_s", &[V128, V128], Some(V128), I::I8x16MinS); + self.inst("i8x16.min_u", &[V128, V128], Some(V128), I::I8x16MinU); + self.inst("i16x8.min_s", &[V128, V128], Some(V128), I::I16x8MinS); + self.inst("i16x8.min_u", &[V128, V128], Some(V128), I::I16x8MinU); + self.inst("i32x4.min_s", &[V128, V128], Some(V128), I::I32x4MinS); + self.inst("i32x4.min_u", &[V128, V128], Some(V128), I::I32x4MinU); + self.inst("f32x4.min", &[V128, V128], Some(V128), I::F32x4Min); + self.inst("f64x2.min", &[V128, V128], Some(V128), I::F64x2Min); + self.inst("f32x4.pmin", &[V128, V128], Some(V128), I::F32x4PMin); + self.inst("f64x2.pmin", &[V128, V128], Some(V128), I::F64x2PMin); + + self.inst("i8x16.max_s", &[V128, V128], Some(V128), I::I8x16MaxS); + self.inst("i8x16.max_u", &[V128, V128], Some(V128), I::I8x16MaxU); + self.inst("i16x8.max_s", &[V128, V128], Some(V128), I::I16x8MaxS); + self.inst("i16x8.max_u", &[V128, V128], Some(V128), I::I16x8MaxU); + self.inst("i32x4.max_s", &[V128, V128], Some(V128), I::I32x4MaxS); + self.inst("i32x4.max_u", &[V128, V128], Some(V128), I::I32x4MaxU); + self.inst("f32x4.max", &[V128, V128], Some(V128), I::F32x4Max); + self.inst("f64x2.max", &[V128, V128], Some(V128), I::F64x2Max); + self.inst("f32x4.pmax", &[V128, V128], Some(V128), I::F32x4PMax); + self.inst("f64x2.pmax", &[V128, V128], Some(V128), I::F64x2PMax); + + self.inst("i8x16.avgr_u", &[V128, V128], Some(V128), I::I8x16RoundingAverageU); + self.inst("i16x8.avgr_u", &[V128, V128], Some(V128), I::I16x8RoundingAverageU); + + self.inst("i8x16.abs", &[V128], Some(V128), I::I8x16Abs); + self.inst("i16x8.abs", &[V128], Some(V128), I::I16x8Abs); + self.inst("i32x4.abs", &[V128], Some(V128), I::I32x4Abs); + self.inst("i64x2.abs", &[V128], Some(V128), I::I64x2Abs); + self.inst("f32x4.abs", &[V128], Some(V128), I::F32x4Abs); + self.inst("f64x2.abs", &[V128], Some(V128), I::F64x2Abs); + + self.inst("i8x16.shl", &[V128, I32], Some(V128), I::I8x16Shl); + self.inst("i16x8.shl", &[V128, I32], Some(V128), I::I16x8Shl); + self.inst("i32x4.shl", &[V128, I32], Some(V128), I::I32x4Shl); + self.inst("i64x2.shl", &[V128, I32], Some(V128), I::I64x2Shl); + + self.inst("i8x16.shr_s", &[V128, I32], Some(V128), I::I8x16ShrS); + self.inst("i8x16.shr_u", &[V128, I32], Some(V128), I::I8x16ShrU); + self.inst("i16x8.shr_s", &[V128, I32], Some(V128), I::I16x8ShrS); + self.inst("i16x8.shr_u", &[V128, I32], Some(V128), I::I16x8ShrU); + self.inst("i32x4.shr_s", &[V128, I32], Some(V128), I::I32x4ShrS); + self.inst("i32x4.shr_u", &[V128, I32], Some(V128), I::I32x4ShrU); + self.inst("i64x2.shr_s", &[V128, I32], Some(V128), I::I64x2ShrS); + self.inst("i64x2.shr_u", &[V128, I32], Some(V128), I::I64x2ShrU); + + self.inst("v128.and", &[V128, V128], Some(V128), I::V128Not); + self.inst("v128.or", &[V128, V128], Some(V128), I::V128Or); + self.inst("v128.xor", &[V128, V128], Some(V128), I::V128Xor); + self.inst("v128.not", &[V128], Some(V128), I::V128Not); + self.inst("v128.andnot", &[V128, V128], Some(V128), I::V128AndNot); + + self.inst("v128.bitselect", &[V128, V128, V128], Some(V128), I::V128Bitselect); + + self.inst("i8x16.popcnt", &[V128], Some(V128), I::I8x16Popcnt); + + self.inst("v128.any_true", &[V128], Some(I32), I::V128AnyTrue); + self.inst("i8x16.all_true", &[V128], Some(I32), I::I8x16AllTrue); + self.inst("i16x8.all_true", &[V128], Some(I32), I::I16x8AllTrue); + self.inst("i32x4.all_true", &[V128], Some(I32), I::I32x4AllTrue); + self.inst("i64x2.all_true", &[V128], Some(I32), I::I64x2AllTrue); + + self.inst("i8x16.bitmask", &[V128], Some(I32), I::I8x16Bitmask); + self.inst("i16x8.bitmask", &[V128], Some(I32), I::I16x8Bitmask); + self.inst("i32x4.bitmask", &[V128], Some(I32), I::I32x4Bitmask); + self.inst("i64x2.bitmask", &[V128], Some(I32), I::I64x2Bitmask); + + self.inst("i8x16.eq", &[V128, V128], Some(V128), I::I8x16Eq); + self.inst("i16x8.eq", &[V128, V128], Some(V128), I::I16x8Eq); + self.inst("i32x4.eq", &[V128, V128], Some(V128), I::I32x4Eq); + self.inst("i64x2.eq", &[V128, V128], Some(V128), I::I64x2Eq); + self.inst("f32x4.eq", &[V128, V128], Some(V128), I::F32x4Eq); + self.inst("f64x2.eq", &[V128, V128], Some(V128), I::F64x2Eq); + + self.inst("i8x16.ne", &[V128, V128], Some(V128), I::I8x16Ne); + self.inst("i16x8.ne", &[V128, V128], Some(V128), I::I16x8Ne); + self.inst("i32x4.ne", &[V128, V128], Some(V128), I::I32x4Ne); + self.inst("i64x2.ne", &[V128, V128], Some(V128), I::I64x2Ne); + self.inst("f32x4.ne", &[V128, V128], Some(V128), I::F32x4Ne); + self.inst("f64x2.ne", &[V128, V128], Some(V128), I::F64x2Ne); + + self.inst("i8x16.lt_s", &[V128, V128], Some(V128), I::I8x16LtS); + self.inst("i8x16.lt_u", &[V128, V128], Some(V128), I::I8x16LtU); + self.inst("i16x8.lt_s", &[V128, V128], Some(V128), I::I16x8LtS); + self.inst("i16x8.lt_u", &[V128, V128], Some(V128), I::I16x8LtU); + self.inst("i32x4.lt_s", &[V128, V128], Some(V128), I::I32x4LtS); + self.inst("i32x4.lt_u", &[V128, V128], Some(V128), I::I32x4LtU); + self.inst("f32x4.lt", &[V128, V128], Some(V128), I::F32x4Lt); + self.inst("f64x2.lt", &[V128, V128], Some(V128), I::F64x2Lt); + + self.inst("i8x16.le_s", &[V128, V128], Some(V128), I::I8x16LeS); + self.inst("i8x16.le_u", &[V128, V128], Some(V128), I::I8x16LeU); + self.inst("i16x8.le_s", &[V128, V128], Some(V128), I::I16x8LeS); + self.inst("i16x8.le_u", &[V128, V128], Some(V128), I::I16x8LeU); + self.inst("i32x4.le_s", &[V128, V128], Some(V128), I::I32x4LeS); + self.inst("i32x4.le_u", &[V128, V128], Some(V128), I::I32x4LeU); + self.inst("f32x4.le", &[V128, V128], Some(V128), I::F32x4Le); + self.inst("f64x2.le", &[V128, V128], Some(V128), I::F64x2Le); + + self.inst("i8x16.gt_s", &[V128, V128], Some(V128), I::I8x16GtS); + self.inst("i8x16.gt_u", &[V128, V128], Some(V128), I::I8x16GtU); + self.inst("i16x8.gt_s", &[V128, V128], Some(V128), I::I16x8GtS); + self.inst("i16x8.gt_u", &[V128, V128], Some(V128), I::I16x8GtU); + self.inst("i32x4.gt_s", &[V128, V128], Some(V128), I::I32x4GtS); + self.inst("i32x4.gt_u", &[V128, V128], Some(V128), I::I32x4GtU); + self.inst("f32x4.gt", &[V128, V128], Some(V128), I::F32x4Gt); + self.inst("f64x2.gt", &[V128, V128], Some(V128), I::F64x2Gt); + + self.inst("i8x16.ge_s", &[V128, V128], Some(V128), I::I8x16GeS); + self.inst("i8x16.ge_u", &[V128, V128], Some(V128), I::I8x16GeU); + self.inst("i16x8.ge_s", &[V128, V128], Some(V128), I::I16x8GeS); + self.inst("i16x8.ge_u", &[V128, V128], Some(V128), I::I16x8GeU); + self.inst("i32x4.ge_s", &[V128, V128], Some(V128), I::I32x4GeS); + self.inst("i32x4.ge_u", &[V128, V128], Some(V128), I::I32x4GeU); + self.inst("f32x4.ge", &[V128, V128], Some(V128), I::F32x4Ge); + self.inst("f64x2.ge", &[V128, V128], Some(V128), I::F64x2Ge); + + self.inst("f32x4.div", &[V128, V128], Some(V128), I::F32x4Div); + self.inst("f64x2.div", &[V128, V128], Some(V128), I::F64x2Div); + + self.inst("f32x4.sqrt", &[V128], Some(V128), I::F32x4Sqrt); + self.inst("f64x2.sqrt", &[V128], Some(V128), I::F64x2Sqrt); + + self.inst("f32x4.ceil", &[V128], Some(V128), I::F32x4Ceil); + self.inst("f64x2.ceil", &[V128], Some(V128), I::F64x2Ceil); + self.inst("f32x4.floor", &[V128], Some(V128), I::F32x4Floor); + self.inst("f64x2.floor", &[V128], Some(V128), I::F64x2Floor); + self.inst("f32x4.trunc", &[V128], Some(V128), I::F32x4Trunc); + self.inst("f64x2.trunc", &[V128], Some(V128), I::F64x2Trunc); + self.inst("f32x4.nearest", &[V128], Some(V128), I::F32x4Nearest); + self.inst("f64x2.nearest", &[V128], Some(V128), I::F64x2Nearest); + + self.inst("f32x4.convert_i32x4_s", &[V128], Some(V128), I::F32x4ConvertI32x4S); + self.inst("f32x4.convert_i32x4_u", &[V128], Some(V128), I::F32x4ConvertI32x4U); + + self.inst("f64x2.convert_low_i32x4_s", &[V128], Some(V128), I::F64x2ConvertLowI32x4S); + self.inst("f64x2.convert_low_i32x4_u", &[V128], Some(V128), I::F64x2ConvertLowI32x4U); + + self.inst("i32x4.trunc_sat_f32x4_s", &[V128], Some(V128), I::I32x4TruncSatF32x4S); + self.inst("i32x4.trunc_sat_f32x4_u", &[V128], Some(V128), I::I32x4TruncSatF32x4U); + + self.inst("i32x4.trunc_sat_f64x2_s_zero", &[V128], Some(V128), I::I32x4TruncSatF64x2SZero); + self.inst("i32x4.trunc_sat_f64x2_u_zero", &[V128], Some(V128), I::I32x4TruncSatF64x2UZero); + + self.inst("f32x4.demote_f64x2_zero", &[V128], Some(V128), I::F32x4DemoteF64x2Zero); + self.inst("f64x2.promote_low_f32x4", &[V128], Some(V128), I::F64x2PromoteLowF32x4); + + self.inst("i8x16.narrow_i16x8_s", &[V128, V128], Some(V128), I::I8x16NarrowI16x8S); + self.inst("i8x16.narrow_i16x8_u", &[V128, V128], Some(V128), I::I8x16NarrowI16x8U); + self.inst("i16x8.narrow_i32x4_s", &[V128, V128], Some(V128), I::I16x8NarrowI32x4S); + self.inst("i16x8.narrow_i32x4_u", &[V128, V128], Some(V128), I::I16x8NarrowI32x4U); + + self.inst("i16x8.extend_low_i8x16_s", &[V128], Some(V128), I::I16x8ExtendLowI8x16S); + self.inst("i16x8.extend_high_i8x16_s", &[V128], Some(V128), I::I16x8ExtendHighI8x16S); + self.inst("i16x8.extend_low_i8x16_u", &[V128], Some(V128), I::I16x8ExtendLowI8x16U); + self.inst("i16x8.extend_high_i8x16_u", &[V128], Some(V128), I::I16x8ExtendHighI8x16U); + self.inst("i32x4.extend_low_i16x8_s", &[V128], Some(V128), I::I32x4ExtendLowI16x8S); + self.inst("i32x4.extend_high_i16x8_s", &[V128], Some(V128), I::I32x4ExtendHighI16x8S); + self.inst("i32x4.extend_low_i16x8_u", &[V128], Some(V128), I::I32x4ExtendLowI16x8U); + self.inst("i32x4.extend_high_i16x8_u", &[V128], Some(V128), I::I32x4ExtendHighI16x8U); + self.inst("i64x2.extend_low_i32x4_s", &[V128], Some(V128), I::I64x2ExtendLowI32x4S); + self.inst("i64x2.extend_high_i32x4_s", &[V128], Some(V128), I::I64x2ExtendHighI32x4S); + self.inst("i64x2.extend_low_i32x4_u", &[V128], Some(V128), I::I64x2ExtendLowI32x4U); + self.inst("i64x2.extend_high_i32x4_u", &[V128], Some(V128), I::I64x2ExtendHighI32x4U); + self.inst( "memory.copy", &[I32, I32, I32], @@ -190,6 +425,32 @@ impl Intrinsics { "i64.load32_u" => MemInstruction::new(I64, I::I64Load32_U, 2), "f32.load" => MemInstruction::new(F32, I::F32Load, 2), "f64.load" => MemInstruction::new(F64, I::F64Load, 3), + "v128.load" => MemInstruction::new(V128, |memarg| I::V128Load { memarg: memarg }, 4), + "v128.load8x8_s" => MemInstruction::new(V128, |memarg| I::V128Load8x8S { memarg: memarg }, 3), + "v128.load8x8_u" => MemInstruction::new(V128, |memarg| I::V128Load8x8U { memarg: memarg }, 3), + "v128.load16x4_s" => MemInstruction::new(V128, |memarg| I::V128Load16x4S { memarg: memarg }, 3), + "v128.load16x4_u" => MemInstruction::new(V128, |memarg| I::V128Load16x4U { memarg: memarg }, 3), + "v128.load32x2_s" => MemInstruction::new(V128, |memarg| I::V128Load32x2S { memarg: memarg }, 3), + "v128.load32x2_u" => MemInstruction::new(V128, |memarg| I::V128Load32x2U { memarg: memarg }, 3), + "v128.load8_splat" => MemInstruction::new(V128, |memarg| I::V128Load8Splat { memarg: memarg }, 0), + "v128.load16_splat" => MemInstruction::new(V128, |memarg| I::V128Load16Splat { memarg: memarg }, 1), + "v128.load32_splat" => MemInstruction::new(V128, |memarg| I::V128Load32Splat { memarg: memarg }, 2), + "v128.load64_splat" => MemInstruction::new(V128, |memarg| I::V128Load64Splat { memarg: memarg }, 3), + "v128.load32_zero" => MemInstruction::new(V128, |memarg| I::V128Load32Zero { memarg: memarg }, 2), + "v128.load64_zero" => MemInstruction::new(V128, |memarg| I::V128Load64Zero { memarg: memarg }, 3), + _ => return None, + }; + return Some(ins); + } + + pub fn find_load_lane(&self, name: &str) -> Option { + use enc::Instruction as I; + use Type::*; + let ins = match name { + "v128.load8_lane" => MemLaneInstruction::new(V128, |memarg, lane| I::V128Load8Lane { memarg: memarg, lane: lane }, 0), + "v128.load16_lane" => MemLaneInstruction::new(V128, |memarg, lane| I::V128Load16Lane { memarg: memarg, lane: lane }, 1), + "v128.load32_lane" => MemLaneInstruction::new(V128, |memarg, lane| I::V128Load32Lane { memarg: memarg, lane: lane }, 2), + "v128.load64_lane" => MemLaneInstruction::new(V128, |memarg, lane| I::V128Load64Lane { memarg: memarg, lane: lane }, 3), _ => return None, }; return Some(ins); @@ -208,6 +469,43 @@ impl Intrinsics { "i64.store32" => MemInstruction::new(I64, I::I64Store32, 2), "f32.store" => MemInstruction::new(F32, I::F32Store, 2), "f64.store" => MemInstruction::new(F64, I::F64Store, 3), + "v128.store" => MemInstruction::new(V128, |memarg| I::V128Store { memarg: memarg }, 4), + _ => return None, + }; + return Some(ins); + } + + pub fn find_store_lane(&self, name: &str) -> Option { + use enc::Instruction as I; + use Type::*; + let ins = match name { + "v128.store8_lane" => MemLaneInstruction::new(V128, |memarg, lane| I::V128Store8Lane { memarg: memarg, lane: lane }, 0), + "v128.store16_lane" => MemLaneInstruction::new(V128, |memarg, lane| I::V128Store16Lane { memarg: memarg, lane: lane }, 1), + "v128.store32_lane" => MemLaneInstruction::new(V128, |memarg, lane| I::V128Store32Lane { memarg: memarg, lane: lane }, 2), + "v128.store64_lane" => MemLaneInstruction::new(V128, |memarg, lane| I::V128Store64Lane { memarg: memarg, lane: lane }, 3), + _ => return None, + }; + return Some(ins); + } + + pub fn find_lane(&self, name: &str) -> Option { + use enc::Instruction as I; + use Type::*; + let ins = match name { + "i8x16.extract_lane_s" => LaneInstruction::new(None, I32, |lane| I::I8x16ExtractLaneS { lane: lane }), + "i8x16.extract_lane_u" => LaneInstruction::new(None, I32, |lane| I::I8x16ExtractLaneU { lane: lane }), + "i8x16.replace_lane" => LaneInstruction::new(Some(I32), V128, |lane| I::I8x16ReplaceLane { lane: lane }), + "i16x8.extract_lane_s" => LaneInstruction::new(None, I32, |lane| I::I16x8ExtractLaneS { lane: lane }), + "i16x8.extract_lane_u" => LaneInstruction::new(None, I32, |lane| I::I16x8ExtractLaneU { lane: lane }), + "i16x8.replace_lane" => LaneInstruction::new(Some(I32), V128, |lane| I::I16x8ReplaceLane { lane: lane }), + "i32x4.extract_lane" => LaneInstruction::new(None, I32, |lane| I::I32x4ExtractLane { lane: lane }), + "i32x4.replace_lane" => LaneInstruction::new(Some(I32), V128, |lane| I::I32x4ReplaceLane { lane: lane }), + "i64x2.extract_lane" => LaneInstruction::new(None, I64, |lane| I::I64x2ExtractLane { lane: lane }), + "i64x2.replace_lane" => LaneInstruction::new(Some(I64), V128, |lane| I::I64x2ReplaceLane { lane: lane }), + "f32x4.extract_lane" => LaneInstruction::new(None, F64, |lane| I::F32x4ExtractLane { lane: lane }), + "f32x4.replace_lane" => LaneInstruction::new(Some(F64), V128, |lane| I::F32x4ReplaceLane { lane: lane }), + "f64x2.extract_lane" => LaneInstruction::new(None, F64, |lane| I::F64x2ExtractLane { lane: lane }), + "f64x2.replace_lane" => LaneInstruction::new(Some(F64), V128, |lane| I::F64x2ReplaceLane { lane: lane }), _ => return None, }; return Some(ins); @@ -233,3 +531,43 @@ impl MemInstruction { } } } + +pub struct MemLaneInstruction { + pub type_: Type, + pub instruction: fn(MemArg, Lane) -> enc::Instruction<'static>, + pub natural_alignment: u32, +} + +impl MemLaneInstruction { + fn new( + type_: Type, + instruction: fn(MemArg, Lane) -> enc::Instruction<'static>, + natural_alignment: u32, + ) -> MemLaneInstruction { + MemLaneInstruction { + type_, + instruction, + natural_alignment, + } + } +} + +pub struct LaneInstruction { + pub param_type: Option, + pub return_type: Type, + pub instruction: fn(Lane) -> enc::Instruction<'static>, +} + +impl LaneInstruction { + fn new( + param_type: Option, + return_type: Type, + instruction: fn(Lane) -> enc::Instruction<'static>, + ) -> LaneInstruction { + LaneInstruction { + param_type, + return_type, + instruction, + } + } +} diff --git a/src/parser.rs b/src/parser.rs index 85d9411..5c7f5dd 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -80,6 +80,7 @@ enum Token { Int(i32), Int64(i64), IntFloat(i32), + Int128(i128), Float(String), Float64(String), Op(String), @@ -108,6 +109,7 @@ impl fmt::Display for Token { Token::Str(s) => write!(f, "{:?}", s), Token::Int(v) => write!(f, "{}", v), Token::Int64(v) => write!(f, "{}", v), + Token::Int128(v) => write!(f, "{}", v), Token::IntFloat(v) => write!(f, "{}_f", v), Token::Float(v) => write!(f, "{}", v), Token::Float64(v) => write!(f, "{}", v), @@ -277,6 +279,11 @@ fn lexer() -> impl Parser, Error = LexerError> { .then_ignore(just("i64")) .map(|n| Token::Int64(n as i64)); + let int128 = integer + .clone() + .then_ignore(just("v128")) + .map(|n| Token::Int128(n as i128)); + let int_float = integer .clone() .then_ignore(just("_f")) @@ -378,7 +385,7 @@ fn lexer() -> impl Parser, Error = LexerError> { let comment = single_line.or(multi_line); let token = choice(( - float, float64, int64, int_float, int, str_, char_, op, ctrl, ident, + float, float64, int64, int128, int_float, int, str_, char_, op, ctrl, ident, )) .recover_with(skip_then_retry_until([])); @@ -484,6 +491,7 @@ fn script_parser() -> impl Parser + Clo let val = map_token(|tok, span| match tok { Token::Int(v) => Some(ast::Expr::I32Const(*v)), Token::Int64(v) => Some(ast::Expr::I64Const(*v)), + Token::Int128(v) => Some(ast::Expr::V128Const(*v)), Token::IntFloat(v) => Some(ast::Expr::Cast { value: Box::new(ast::Expr::I32Const(*v).with_span(span.clone())), type_: ast::Type::F32, @@ -1167,6 +1175,7 @@ fn type_parser() -> impl Parser + Clone { Token::Ident(id) if id == "i64" => Ok(ast::Type::I64), Token::Ident(id) if id == "f32" => Ok(ast::Type::F32), Token::Ident(id) if id == "f64" => Ok(ast::Type::F64), + Token::Ident(id) if id == "v128" => Ok(ast::Type::V128), _ => Err(ScriptError::expected_input_found( span, vec![ @@ -1174,6 +1183,7 @@ fn type_parser() -> impl Parser + Clone { Some(Token::Ident("i64".into())), Some(Token::Ident("f32".into())), Some(Token::Ident("f64".into())), + Some(Token::Ident("v128".into())), ], Some(tok), )), diff --git a/src/typecheck.rs b/src/typecheck.rs index 17abab8..fde7d2f 100644 --- a/src/typecheck.rs +++ b/src/typecheck.rs @@ -501,6 +501,7 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<() ast::Expr::I64Const(_) => Some(ast::Type::I64), ast::Expr::F32Const(_) => Some(ast::Type::F32), ast::Expr::F64Const(_) => Some(ast::Type::F64), + ast::Expr::V128Const(_) => Some(ast::Type::V128), ast::Expr::UnaryOp { op, ref mut value } => { tc_expression(context, value)?; if value.type_.is_none() { @@ -719,6 +720,14 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<() if let Some(load) = context.intrinsics.find_load(name) { tc_memarg(context, params.as_mut_slice(), &expr.span)?; Some(load.type_) + } else if let Some(load_lane) = context.intrinsics.find_load_lane(name) { + if let Some(value) = params.first_mut() { + tc_lane(context, value, &expr.span)?; + } else { + return report_error("Missing parameters", &expr.span, context.sources); + } + tc_memarg(context, &mut params[1..], &expr.span)?; + Some(load_lane.type_) } else if let Some(store) = context.intrinsics.find_store(name) { if let Some(value) = params.first_mut() { tc_expression(context, value)?; @@ -736,6 +745,65 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<() } tc_memarg(context, &mut params[1..], &expr.span)?; None + } else if let Some(store_lane) = context.intrinsics.find_store_lane(name) { + if let Some(value) = params.first_mut() { + tc_expression(context, value)?; + if value.type_ != Some(store_lane.type_) { + type_mismatch( + Some(store_lane.type_), + &expr.span, + value.type_, + &value.span, + context.sources, + )?; + } + } else { + return report_error("Missing parameters", &expr.span, context.sources); + } + if let Some(value) = params.get_mut(1) { + tc_lane(context, value, &expr.span)?; + } else { + return report_error("Missing parameters", &expr.span, context.sources); + } + tc_memarg(context, &mut params[2..], &expr.span)?; + None + } else if let Some(lane_instr) = context.intrinsics.find_lane(name) { + if let Some(value) = params.first_mut() { + tc_expression(context, value)?; + if value.type_ != Some(V128) { + type_mismatch( + Some(V128), + &expr.span, + value.type_, + &value.span, + context.sources, + )?; + } + } else { + return report_error("Missing parameters", &expr.span, context.sources); + } + if let Some(value) = params.get_mut(1) { + tc_lane(context, value, &expr.span)?; + } else { + return report_error("Missing parameters", &expr.span, context.sources); + } + if let Some(param_type) = lane_instr.param_type { + if let Some(value) = params.get_mut(2) { + tc_expression(context, value)?; + if value.type_ != Some(param_type) { + type_mismatch( + Some(param_type), + &expr.span, + value.type_, + &value.span, + context.sources, + )?; + } + } else { + return report_error("Missing parameters", &expr.span, context.sources); + } + } + Some(lane_instr.return_type) } else if let Some(type_map) = context .functions .get(name) @@ -893,6 +961,7 @@ fn tc_const(expr: &mut ast::Expression, sources: &Sources) -> Result<()> { I64Const(_) => I64, F32Const(_) => F32, F64Const(_) => F64, + V128Const(_) => V128, _ => return report_error("Expected constant value", &expr.span, sources), }); Ok(()) @@ -930,3 +999,18 @@ fn tc_memarg(context: &mut Context, params: &mut [ast::Expression], span: &Span) Ok(()) } + +fn tc_lane(context: &mut Context, param: &mut ast::Expression, span: &Span) -> Result<()> { + if param.type_ != Some(I32) { + return type_mismatch(Some(I32), &span, param.type_, ¶m.span, context.sources); + } + let lane = param.const_i32(); + if lane < 0 || lane > 15 { + return report_error( + &format!("Lane {} out of range (0-15)", lane), + ¶m.span, + context.sources, + ); + } + Ok(()) +} From 1bad500418a464ff3dd2123f8edba7fa57301ebd Mon Sep 17 00:00:00 2001 From: luchak Date: Mon, 23 Jan 2023 22:50:12 -0800 Subject: [PATCH 02/11] Fix load lane instructions --- src/emit.rs | 7 ++++--- src/intrinsics.rs | 21 ++++++++------------- src/typecheck.rs | 26 ++++++++++++++++++++------ 3 files changed, 32 insertions(+), 22 deletions(-) diff --git a/src/emit.rs b/src/emit.rs index 15c6598..a78e6fb 100644 --- a/src/emit.rs +++ b/src/emit.rs @@ -714,13 +714,14 @@ fn emit_expression<'a>(ctx: &mut FunctionContext<'a>, expr: &'a ast::Expression) ctx.function .instruction(&mem_instruction(load, ¶ms[1..])); } else if let Some(load_lane) = ctx.intrinsics.find_load_lane(name) { + emit_expression(ctx, ¶ms[2]); + emit_expression(ctx, ¶ms[0]); let lane = params - .get(0) + .get(1) .map(|e| e.const_i32() as u8) .unwrap(); - emit_expression(ctx, ¶ms[1]); ctx.function - .instruction(&mem_lane_instruction(load_lane, lane, ¶ms[2..])); + .instruction(&mem_lane_instruction(load_lane, lane, ¶ms[3..])); } else if let Some(store) = ctx.intrinsics.find_store(name) { emit_expression(ctx, ¶ms[1]); emit_expression(ctx, ¶ms[0]); diff --git a/src/intrinsics.rs b/src/intrinsics.rs index e9d977b..77270ec 100644 --- a/src/intrinsics.rs +++ b/src/intrinsics.rs @@ -445,12 +445,11 @@ impl Intrinsics { pub fn find_load_lane(&self, name: &str) -> Option { use enc::Instruction as I; - use Type::*; let ins = match name { - "v128.load8_lane" => MemLaneInstruction::new(V128, |memarg, lane| I::V128Load8Lane { memarg: memarg, lane: lane }, 0), - "v128.load16_lane" => MemLaneInstruction::new(V128, |memarg, lane| I::V128Load16Lane { memarg: memarg, lane: lane }, 1), - "v128.load32_lane" => MemLaneInstruction::new(V128, |memarg, lane| I::V128Load32Lane { memarg: memarg, lane: lane }, 2), - "v128.load64_lane" => MemLaneInstruction::new(V128, |memarg, lane| I::V128Load64Lane { memarg: memarg, lane: lane }, 3), + "v128.load8_lane" => MemLaneInstruction::new(|memarg, lane| I::V128Load8Lane { memarg: memarg, lane: lane }, 0), + "v128.load16_lane" => MemLaneInstruction::new(|memarg, lane| I::V128Load16Lane { memarg: memarg, lane: lane }, 1), + "v128.load32_lane" => MemLaneInstruction::new(|memarg, lane| I::V128Load32Lane { memarg: memarg, lane: lane }, 2), + "v128.load64_lane" => MemLaneInstruction::new(|memarg, lane| I::V128Load64Lane { memarg: memarg, lane: lane }, 3), _ => return None, }; return Some(ins); @@ -477,12 +476,11 @@ impl Intrinsics { pub fn find_store_lane(&self, name: &str) -> Option { use enc::Instruction as I; - use Type::*; let ins = match name { - "v128.store8_lane" => MemLaneInstruction::new(V128, |memarg, lane| I::V128Store8Lane { memarg: memarg, lane: lane }, 0), - "v128.store16_lane" => MemLaneInstruction::new(V128, |memarg, lane| I::V128Store16Lane { memarg: memarg, lane: lane }, 1), - "v128.store32_lane" => MemLaneInstruction::new(V128, |memarg, lane| I::V128Store32Lane { memarg: memarg, lane: lane }, 2), - "v128.store64_lane" => MemLaneInstruction::new(V128, |memarg, lane| I::V128Store64Lane { memarg: memarg, lane: lane }, 3), + "v128.store8_lane" => MemLaneInstruction::new(|memarg, lane| I::V128Store8Lane { memarg: memarg, lane: lane }, 0), + "v128.store16_lane" => MemLaneInstruction::new(|memarg, lane| I::V128Store16Lane { memarg: memarg, lane: lane }, 1), + "v128.store32_lane" => MemLaneInstruction::new(|memarg, lane| I::V128Store32Lane { memarg: memarg, lane: lane }, 2), + "v128.store64_lane" => MemLaneInstruction::new(|memarg, lane| I::V128Store64Lane { memarg: memarg, lane: lane }, 3), _ => return None, }; return Some(ins); @@ -533,19 +531,16 @@ impl MemInstruction { } pub struct MemLaneInstruction { - pub type_: Type, pub instruction: fn(MemArg, Lane) -> enc::Instruction<'static>, pub natural_alignment: u32, } impl MemLaneInstruction { fn new( - type_: Type, instruction: fn(MemArg, Lane) -> enc::Instruction<'static>, natural_alignment: u32, ) -> MemLaneInstruction { MemLaneInstruction { - type_, instruction, natural_alignment, } diff --git a/src/typecheck.rs b/src/typecheck.rs index fde7d2f..35a9c6f 100644 --- a/src/typecheck.rs +++ b/src/typecheck.rs @@ -720,14 +720,28 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<() if let Some(load) = context.intrinsics.find_load(name) { tc_memarg(context, params.as_mut_slice(), &expr.span)?; Some(load.type_) - } else if let Some(load_lane) = context.intrinsics.find_load_lane(name) { + } else if let Some(_) = context.intrinsics.find_load_lane(name) { if let Some(value) = params.first_mut() { + tc_expression(context, value)?; + if value.type_ != Some(V128) { + type_mismatch( + Some(V128), + &expr.span, + value.type_, + &value.span, + context.sources, + )?; + } + } else { + return report_error("Missing parameters", &expr.span, context.sources); + } + if let Some(value) = params.get_mut(1) { tc_lane(context, value, &expr.span)?; } else { return report_error("Missing parameters", &expr.span, context.sources); } - tc_memarg(context, &mut params[1..], &expr.span)?; - Some(load_lane.type_) + tc_memarg(context, &mut params[2..], &expr.span)?; + Some(V128) } else if let Some(store) = context.intrinsics.find_store(name) { if let Some(value) = params.first_mut() { tc_expression(context, value)?; @@ -745,12 +759,12 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<() } tc_memarg(context, &mut params[1..], &expr.span)?; None - } else if let Some(store_lane) = context.intrinsics.find_store_lane(name) { + } else if let Some(_) = context.intrinsics.find_store_lane(name) { if let Some(value) = params.first_mut() { tc_expression(context, value)?; - if value.type_ != Some(store_lane.type_) { + if value.type_ != Some(V128) { type_mismatch( - Some(store_lane.type_), + Some(V128), &expr.span, value.type_, &value.span, From bdcf09b4959f4b29ac9689eaee9902f0462b1a73 Mon Sep 17 00:00:00 2001 From: luchak Date: Mon, 23 Jan 2023 23:39:00 -0800 Subject: [PATCH 03/11] Use i128 to mark 128-bit literals. --- src/parser.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parser.rs b/src/parser.rs index 5c7f5dd..2f6feab 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -281,7 +281,7 @@ fn lexer() -> impl Parser, Error = LexerError> { let int128 = integer .clone() - .then_ignore(just("v128")) + .then_ignore(just("i128")) .map(|n| Token::Int128(n as i128)); let int_float = integer From c5d129210c7561dc29aca26bbbe625d462bd4664 Mon Sep 17 00:00:00 2001 From: luchak Date: Mon, 23 Jan 2023 23:58:00 -0800 Subject: [PATCH 04/11] Update README --- README.md | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 7c7d597..e311f84 100644 --- a/README.md +++ b/README.md @@ -66,12 +66,13 @@ include "platform_imports.cwa" ### Types -There are four types in WebAssembly and therefore CurlyWas: +There are five types in WebAssembly and therefore CurlyWas: * `i32`: 32bit integer * `i64`: 64bit integer * `f32`: 32bit float * `f64`: 64bit float +* `v128`: 128bit vector There are no unsigned types, but there are unsigned operators where it makes a difference. @@ -83,6 +84,12 @@ Integer numbers can be given either in decimal or hex: 123, -7878, 0xf00 ``` +64 or 128-bit integers are denoted using `i64` or `i128`: + +``` +0xabc0i64, 8i128 +``` + For floating point numbers, only the most basic decimal format is currently implemented (no scientific notation or hex-floats, yet): ``` @@ -399,6 +406,19 @@ And binary files: file("font.bin") ``` +#### SIMD + +Intrinsics are available for all WASM SIMD instructions, except for `i8x16.shuffle` and the relaxed SIMD extensions. +For instructions that refer to lanes, i.e. `*.extract_lane*`, `*.replace_lane`, `v128.store*_lane`, and +`v128.load*_lane`, the lane number follows the vector argument. For example: + +``` +v128.store32_lane(, , [, , []]); +v128.load32_splat(, , [, , []]); +i32x4.extract_lane(, ); +i32x4.replace_lane(, , ); +``` + #### Advanced sequencing Sometimes when sizeoptimizing it helps to be able to execute some side-effecty code in the middle an expression. @@ -430,7 +450,6 @@ The idea of CurlyWas is to be able to hand-craft any valid WASM program, ie. hav This goal is not yet fully reached, with the following being the main limitations: -* CurlyWas currently only targets MVP web assembly + non-trapping float-to-int conversions. No other post-MVP features are currently supported. Especially "Multi-value" will be problematic as this allows programs that don't map cleanly to an expression tree. -* Memory intrinsics are still missing, so only (unsigned) 8 and 32 bit integer reads and writes are possible. +* CurlyWas currently only targets MVP web assembly + non-trapping float-to-int conversions + SIMD. No other post-MVP features are currently supported. Especially "Multi-value" will be problematic as this allows programs that don't map cleanly to an expression tree. * `block`s cannot return values, as the branch instructions are missing syntax to pass along a value. -* `br_table` and `call_indirect` are not yet implemented. \ No newline at end of file +* `br_table` and `call_indirect` are not yet implemented. From 7c17d6228a68bb0905e09394698daa833eca1080 Mon Sep 17 00:00:00 2001 From: luchak Date: Tue, 24 Jan 2023 08:03:32 -0800 Subject: [PATCH 05/11] Check lane widths --- src/intrinsics.rs | 50 ++++++++++++++++++++++++++--------------------- src/typecheck.rs | 17 ++++++++-------- 2 files changed, 37 insertions(+), 30 deletions(-) diff --git a/src/intrinsics.rs b/src/intrinsics.rs index 77270ec..56fd318 100644 --- a/src/intrinsics.rs +++ b/src/intrinsics.rs @@ -446,10 +446,10 @@ impl Intrinsics { pub fn find_load_lane(&self, name: &str) -> Option { use enc::Instruction as I; let ins = match name { - "v128.load8_lane" => MemLaneInstruction::new(|memarg, lane| I::V128Load8Lane { memarg: memarg, lane: lane }, 0), - "v128.load16_lane" => MemLaneInstruction::new(|memarg, lane| I::V128Load16Lane { memarg: memarg, lane: lane }, 1), - "v128.load32_lane" => MemLaneInstruction::new(|memarg, lane| I::V128Load32Lane { memarg: memarg, lane: lane }, 2), - "v128.load64_lane" => MemLaneInstruction::new(|memarg, lane| I::V128Load64Lane { memarg: memarg, lane: lane }, 3), + "v128.load8_lane" => MemLaneInstruction::new(|memarg, lane| I::V128Load8Lane { memarg: memarg, lane: lane }, 0, 0), + "v128.load16_lane" => MemLaneInstruction::new(|memarg, lane| I::V128Load16Lane { memarg: memarg, lane: lane }, 1, 1), + "v128.load32_lane" => MemLaneInstruction::new(|memarg, lane| I::V128Load32Lane { memarg: memarg, lane: lane }, 2, 2), + "v128.load64_lane" => MemLaneInstruction::new(|memarg, lane| I::V128Load64Lane { memarg: memarg, lane: lane }, 3, 3), _ => return None, }; return Some(ins); @@ -477,10 +477,10 @@ impl Intrinsics { pub fn find_store_lane(&self, name: &str) -> Option { use enc::Instruction as I; let ins = match name { - "v128.store8_lane" => MemLaneInstruction::new(|memarg, lane| I::V128Store8Lane { memarg: memarg, lane: lane }, 0), - "v128.store16_lane" => MemLaneInstruction::new(|memarg, lane| I::V128Store16Lane { memarg: memarg, lane: lane }, 1), - "v128.store32_lane" => MemLaneInstruction::new(|memarg, lane| I::V128Store32Lane { memarg: memarg, lane: lane }, 2), - "v128.store64_lane" => MemLaneInstruction::new(|memarg, lane| I::V128Store64Lane { memarg: memarg, lane: lane }, 3), + "v128.store8_lane" => MemLaneInstruction::new(|memarg, lane| I::V128Store8Lane { memarg: memarg, lane: lane }, 0, 0), + "v128.store16_lane" => MemLaneInstruction::new(|memarg, lane| I::V128Store16Lane { memarg: memarg, lane: lane }, 1, 1), + "v128.store32_lane" => MemLaneInstruction::new(|memarg, lane| I::V128Store32Lane { memarg: memarg, lane: lane }, 2, 2), + "v128.store64_lane" => MemLaneInstruction::new(|memarg, lane| I::V128Store64Lane { memarg: memarg, lane: lane }, 3, 3), _ => return None, }; return Some(ins); @@ -490,20 +490,20 @@ impl Intrinsics { use enc::Instruction as I; use Type::*; let ins = match name { - "i8x16.extract_lane_s" => LaneInstruction::new(None, I32, |lane| I::I8x16ExtractLaneS { lane: lane }), - "i8x16.extract_lane_u" => LaneInstruction::new(None, I32, |lane| I::I8x16ExtractLaneU { lane: lane }), - "i8x16.replace_lane" => LaneInstruction::new(Some(I32), V128, |lane| I::I8x16ReplaceLane { lane: lane }), - "i16x8.extract_lane_s" => LaneInstruction::new(None, I32, |lane| I::I16x8ExtractLaneS { lane: lane }), - "i16x8.extract_lane_u" => LaneInstruction::new(None, I32, |lane| I::I16x8ExtractLaneU { lane: lane }), - "i16x8.replace_lane" => LaneInstruction::new(Some(I32), V128, |lane| I::I16x8ReplaceLane { lane: lane }), - "i32x4.extract_lane" => LaneInstruction::new(None, I32, |lane| I::I32x4ExtractLane { lane: lane }), - "i32x4.replace_lane" => LaneInstruction::new(Some(I32), V128, |lane| I::I32x4ReplaceLane { lane: lane }), - "i64x2.extract_lane" => LaneInstruction::new(None, I64, |lane| I::I64x2ExtractLane { lane: lane }), - "i64x2.replace_lane" => LaneInstruction::new(Some(I64), V128, |lane| I::I64x2ReplaceLane { lane: lane }), - "f32x4.extract_lane" => LaneInstruction::new(None, F64, |lane| I::F32x4ExtractLane { lane: lane }), - "f32x4.replace_lane" => LaneInstruction::new(Some(F64), V128, |lane| I::F32x4ReplaceLane { lane: lane }), - "f64x2.extract_lane" => LaneInstruction::new(None, F64, |lane| I::F64x2ExtractLane { lane: lane }), - "f64x2.replace_lane" => LaneInstruction::new(Some(F64), V128, |lane| I::F64x2ReplaceLane { lane: lane }), + "i8x16.extract_lane_s" => LaneInstruction::new(None, I32, |lane| I::I8x16ExtractLaneS { lane: lane }, 0), + "i8x16.extract_lane_u" => LaneInstruction::new(None, I32, |lane| I::I8x16ExtractLaneU { lane: lane }, 0), + "i8x16.replace_lane" => LaneInstruction::new(Some(I32), V128, |lane| I::I8x16ReplaceLane { lane: lane }, 0), + "i16x8.extract_lane_s" => LaneInstruction::new(None, I32, |lane| I::I16x8ExtractLaneS { lane: lane }, 1), + "i16x8.extract_lane_u" => LaneInstruction::new(None, I32, |lane| I::I16x8ExtractLaneU { lane: lane }, 1), + "i16x8.replace_lane" => LaneInstruction::new(Some(I32), V128, |lane| I::I16x8ReplaceLane { lane: lane }, 1), + "i32x4.extract_lane" => LaneInstruction::new(None, I32, |lane| I::I32x4ExtractLane { lane: lane }, 2), + "i32x4.replace_lane" => LaneInstruction::new(Some(I32), V128, |lane| I::I32x4ReplaceLane { lane: lane }, 2), + "i64x2.extract_lane" => LaneInstruction::new(None, I64, |lane| I::I64x2ExtractLane { lane: lane }, 3), + "i64x2.replace_lane" => LaneInstruction::new(Some(I64), V128, |lane| I::I64x2ReplaceLane { lane: lane }, 3), + "f32x4.extract_lane" => LaneInstruction::new(None, F64, |lane| I::F32x4ExtractLane { lane: lane }, 2), + "f32x4.replace_lane" => LaneInstruction::new(Some(F64), V128, |lane| I::F32x4ReplaceLane { lane: lane }, 2), + "f64x2.extract_lane" => LaneInstruction::new(None, F64, |lane| I::F64x2ExtractLane { lane: lane }, 3), + "f64x2.replace_lane" => LaneInstruction::new(Some(F64), V128, |lane| I::F64x2ReplaceLane { lane: lane }, 3), _ => return None, }; return Some(ins); @@ -533,16 +533,19 @@ impl MemInstruction { pub struct MemLaneInstruction { pub instruction: fn(MemArg, Lane) -> enc::Instruction<'static>, pub natural_alignment: u32, + pub lane_width: u32, } impl MemLaneInstruction { fn new( instruction: fn(MemArg, Lane) -> enc::Instruction<'static>, natural_alignment: u32, + lane_width: u32, ) -> MemLaneInstruction { MemLaneInstruction { instruction, natural_alignment, + lane_width } } } @@ -551,6 +554,7 @@ pub struct LaneInstruction { pub param_type: Option, pub return_type: Type, pub instruction: fn(Lane) -> enc::Instruction<'static>, + pub lane_width: u32, } impl LaneInstruction { @@ -558,11 +562,13 @@ impl LaneInstruction { param_type: Option, return_type: Type, instruction: fn(Lane) -> enc::Instruction<'static>, + lane_width: u32, ) -> LaneInstruction { LaneInstruction { param_type, return_type, instruction, + lane_width, } } } diff --git a/src/typecheck.rs b/src/typecheck.rs index 35a9c6f..2320362 100644 --- a/src/typecheck.rs +++ b/src/typecheck.rs @@ -720,7 +720,7 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<() if let Some(load) = context.intrinsics.find_load(name) { tc_memarg(context, params.as_mut_slice(), &expr.span)?; Some(load.type_) - } else if let Some(_) = context.intrinsics.find_load_lane(name) { + } else if let Some(load_lane) = context.intrinsics.find_load_lane(name) { if let Some(value) = params.first_mut() { tc_expression(context, value)?; if value.type_ != Some(V128) { @@ -736,7 +736,7 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<() return report_error("Missing parameters", &expr.span, context.sources); } if let Some(value) = params.get_mut(1) { - tc_lane(context, value, &expr.span)?; + tc_lane(load_lane.lane_width, context, value, &expr.span)?; } else { return report_error("Missing parameters", &expr.span, context.sources); } @@ -759,7 +759,7 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<() } tc_memarg(context, &mut params[1..], &expr.span)?; None - } else if let Some(_) = context.intrinsics.find_store_lane(name) { + } else if let Some(store_lane) = context.intrinsics.find_store_lane(name) { if let Some(value) = params.first_mut() { tc_expression(context, value)?; if value.type_ != Some(V128) { @@ -775,7 +775,7 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<() return report_error("Missing parameters", &expr.span, context.sources); } if let Some(value) = params.get_mut(1) { - tc_lane(context, value, &expr.span)?; + tc_lane(store_lane.lane_width, context, value, &expr.span)?; } else { return report_error("Missing parameters", &expr.span, context.sources); } @@ -797,7 +797,7 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<() return report_error("Missing parameters", &expr.span, context.sources); } if let Some(value) = params.get_mut(1) { - tc_lane(context, value, &expr.span)?; + tc_lane(lane_instr.lane_width, context, value, &expr.span)?; } else { return report_error("Missing parameters", &expr.span, context.sources); } @@ -1014,14 +1014,15 @@ fn tc_memarg(context: &mut Context, params: &mut [ast::Expression], span: &Span) Ok(()) } -fn tc_lane(context: &mut Context, param: &mut ast::Expression, span: &Span) -> Result<()> { +fn tc_lane(lane_width: u32, context: &mut Context, param: &mut ast::Expression, span: &Span) -> Result<()> { if param.type_ != Some(I32) { return type_mismatch(Some(I32), &span, param.type_, ¶m.span, context.sources); } let lane = param.const_i32(); - if lane < 0 || lane > 15 { + let max_lane = (16 >> lane_width) - 1; + if lane < 0 || lane > max_lane { return report_error( - &format!("Lane {} out of range (0-15)", lane), + &format!("Lane {} out of range (0-{})", lane, max_lane), ¶m.span, context.sources, ); From 1072d6c86fc6cf515895518a7ae2b45a0c0f3ac7 Mon Sep 17 00:00:00 2001 From: luchak Date: Tue, 24 Jan 2023 08:13:07 -0800 Subject: [PATCH 06/11] Ensure that lane parameters are constants --- src/typecheck.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/typecheck.rs b/src/typecheck.rs index 2320362..e426f9f 100644 --- a/src/typecheck.rs +++ b/src/typecheck.rs @@ -1018,6 +1018,7 @@ fn tc_lane(lane_width: u32, context: &mut Context, param: &mut ast::Expression, if param.type_ != Some(I32) { return type_mismatch(Some(I32), &span, param.type_, ¶m.span, context.sources); } + tc_const(param, context.sources)?; let lane = param.const_i32(); let max_lane = (16 >> lane_width) - 1; if lane < 0 || lane > max_lane { From 830001d8a514d73a48545274c10cdfecb89a3ac3 Mon Sep 17 00:00:00 2001 From: luchak Date: Tue, 24 Jan 2023 08:30:54 -0800 Subject: [PATCH 07/11] Parse 128-bit integers --- src/parser.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/parser.rs b/src/parser.rs index 2f6feab..bab2494 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -79,8 +79,8 @@ enum Token { Str(String), Int(i32), Int64(i64), - IntFloat(i32), Int128(i128), + IntFloat(i32), Float(String), Float64(String), Op(String), @@ -266,10 +266,10 @@ fn lexer() -> impl Parser, Error = LexerError> { let integer = just::<_, _, LexerError>("0x") .ignore_then(text::digits(16)) .try_map(|n, span| { - u64::from_str_radix(&n, 16).map_err(|err| LexerError::custom(span, err.to_string())) + u128::from_str_radix(&n, 16).map_err(|err| LexerError::custom(span, err.to_string())) }) .or(text::digits(10).try_map(|n: String, span: Span| { - n.parse::() + n.parse::() .map_err(|err| LexerError::custom(span, err.to_string())) })) .boxed(); From 27a2f8ad5491eff396374fe5e2341b1b67583366 Mon Sep 17 00:00:00 2001 From: luchak Date: Tue, 24 Jan 2023 08:43:11 -0800 Subject: [PATCH 08/11] Support i128 in data sections --- src/ast.rs | 8 ++++++++ src/emit.rs | 3 +++ src/parser.rs | 1 + src/typecheck.rs | 1 + 4 files changed, 13 insertions(+) diff --git a/src/ast.rs b/src/ast.rs index 5ea2b98..8c9c09a 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -177,6 +177,7 @@ pub enum DataType { I16, I32, I64, + I128, F32, F64, } @@ -211,6 +212,13 @@ impl Expression { } } + pub fn const_v128(&self) -> i128 { + match self.expr { + Expr::V128Const(v) => v, + _ => panic!("Expected V128Const"), + } + } + pub fn const_f32(&self) -> f32 { match self.expr { Expr::F32Const(v) => v, diff --git a/src/emit.rs b/src/emit.rs index a78e6fb..139cc99 100644 --- a/src/emit.rs +++ b/src/emit.rs @@ -146,6 +146,7 @@ pub fn emit(script: &ast::Script, module_name: &str, options: &Options) -> Vec 2, ast::DataType::I32 => 4, ast::DataType::I64 => 8, + ast::DataType::I128 => 16, ast::DataType::F32 => 4, ast::DataType::F64 => 8, }; @@ -161,6 +162,8 @@ pub fn emit(script: &ast::Script, module_name: &str, options: &Options) -> Vec segment_data .extend_from_slice(&(value.const_i64() as u64).to_le_bytes()), + ast::DataType::I128 => segment_data + .extend_from_slice(&(value.const_v128() as u128).to_le_bytes()), ast::DataType::F32 => { segment_data.extend_from_slice(&value.const_f32().to_le_bytes()) } diff --git a/src/parser.rs b/src/parser.rs index bab2494..041e9df 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1099,6 +1099,7 @@ fn script_parser() -> impl Parser + Clo .or(just(Token::Ident("i16".to_string())).to(ast::DataType::I16)) .or(just(Token::Ident("i32".to_string())).to(ast::DataType::I32)) .or(just(Token::Ident("i64".to_string())).to(ast::DataType::I64)) + .or(just(Token::Ident("i128".to_string())).to(ast::DataType::I128)) .or(just(Token::Ident("f32".to_string())).to(ast::DataType::F32)) .or(just(Token::Ident("f64".to_string())).to(ast::DataType::F64)) .then( diff --git a/src/typecheck.rs b/src/typecheck.rs index e426f9f..b61baed 100644 --- a/src/typecheck.rs +++ b/src/typecheck.rs @@ -231,6 +231,7 @@ pub fn tc_script(script: &mut ast::Script, sources: &Sources) -> Result<()> { ast::Type::I32 } ast::DataType::I64 => ast::Type::I64, + ast::DataType::I128 => ast::Type::V128, ast::DataType::F32 => ast::Type::F32, ast::DataType::F64 => ast::Type::F64, }; From 51999301e43e5850f7657cd9d4a22b886b3defbc Mon Sep 17 00:00:00 2001 From: luchak Date: Tue, 24 Jan 2023 17:14:45 -0800 Subject: [PATCH 09/11] Implement i8x16.shuffle Also change the way lane widths are handled - it turns out that shuffle takes lane arguments up to 31, so it's necessary to store maximum lane index explicitly instead of deriving it from lane width. --- README.md | 10 +++++- src/emit.rs | 17 +++++++++- src/intrinsics.rs | 83 ++++++++++++++++++++++++++++++----------------- src/typecheck.rs | 35 ++++++++++++++++---- 4 files changed, 107 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index e311f84..50c8063 100644 --- a/README.md +++ b/README.md @@ -408,7 +408,7 @@ file("font.bin") #### SIMD -Intrinsics are available for all WASM SIMD instructions, except for `i8x16.shuffle` and the relaxed SIMD extensions. +Intrinsics are available for all WASM SIMD instructions, except for the relaxed SIMD extensions. For instructions that refer to lanes, i.e. `*.extract_lane*`, `*.replace_lane`, `v128.store*_lane`, and `v128.load*_lane`, the lane number follows the vector argument. For example: @@ -419,6 +419,14 @@ i32x4.extract_lane(, ); i32x4.replace_lane(, , ); ``` +The format for `i8x16.shuffle` is: + +``` +i8x16.shuffle(, , [[, [, ... ]]]) +``` + +Omitted lane arguments default to their index, so providing no lane arguments simply returns the value of ``. + #### Advanced sequencing Sometimes when sizeoptimizing it helps to be able to execute some side-effecty code in the middle an expression. diff --git a/src/emit.rs b/src/emit.rs index 139cc99..c6ffdd4 100644 --- a/src/emit.rs +++ b/src/emit.rs @@ -8,7 +8,7 @@ use wasm_encoder::{ use crate::{ ast, - intrinsics::{Intrinsics, MemInstruction, MemLaneInstruction, LaneInstruction}, + intrinsics::{Intrinsics, LaneInstruction, MemInstruction, MemLaneInstruction, ShuffleInstruction}, Options, }; @@ -712,6 +712,16 @@ fn emit_expression<'a>(ctx: &mut FunctionContext<'a>, expr: &'a ast::Expression) ) -> Instruction<'static> { (inst.instruction)(lane) } + fn shuffle_instruction( + inst: ShuffleInstruction, + instr_lanes: &[ast::Expression], + ) -> Instruction<'static> { + let mut lanes: [u8; 16] = [0; 16]; + for (elem, i) in lanes.iter_mut().zip(0..16) { + *elem = instr_lanes.get(i).map(|e| e.const_i32() as u8).unwrap_or(i as u8); + } + (inst.instruction)(lanes) + } if let Some(load) = ctx.intrinsics.find_load(name) { emit_expression(ctx, ¶ms[0]); ctx.function @@ -750,6 +760,11 @@ fn emit_expression<'a>(ctx: &mut FunctionContext<'a>, expr: &'a ast::Expression) } ctx.function .instruction(&lane_instruction(lane_instr, lane)); + } else if let Some(shuffle) = ctx.intrinsics.find_shuffle(name) { + emit_expression(ctx, ¶ms[0]); + emit_expression(ctx, ¶ms[1]); + ctx.function + .instruction(&shuffle_instruction(shuffle, ¶ms[2..])); } else { for param in params { emit_expression(ctx, param); diff --git a/src/intrinsics.rs b/src/intrinsics.rs index 56fd318..d42b18e 100644 --- a/src/intrinsics.rs +++ b/src/intrinsics.rs @@ -140,7 +140,6 @@ impl Intrinsics { self.inst("f32x4.splat", &[F32], Some(V128), I::F32x4Splat); self.inst("f64x2.splat", &[F64], Some(V128), I::F64x2Splat); - // skipped: i8x16.shuffle (requires special handling for 16 literal lane values) self.inst("i8x16.swizzle", &[V128, V128], Some(V128), I::I8x16Swizzle); self.inst("i8x16.add", &[V128, V128], Some(V128), I::I8x16Add); @@ -446,10 +445,10 @@ impl Intrinsics { pub fn find_load_lane(&self, name: &str) -> Option { use enc::Instruction as I; let ins = match name { - "v128.load8_lane" => MemLaneInstruction::new(|memarg, lane| I::V128Load8Lane { memarg: memarg, lane: lane }, 0, 0), - "v128.load16_lane" => MemLaneInstruction::new(|memarg, lane| I::V128Load16Lane { memarg: memarg, lane: lane }, 1, 1), - "v128.load32_lane" => MemLaneInstruction::new(|memarg, lane| I::V128Load32Lane { memarg: memarg, lane: lane }, 2, 2), - "v128.load64_lane" => MemLaneInstruction::new(|memarg, lane| I::V128Load64Lane { memarg: memarg, lane: lane }, 3, 3), + "v128.load8_lane" => MemLaneInstruction::new(|memarg, lane| I::V128Load8Lane { memarg: memarg, lane: lane }, 0, 16), + "v128.load16_lane" => MemLaneInstruction::new(|memarg, lane| I::V128Load16Lane { memarg: memarg, lane: lane }, 1, 8), + "v128.load32_lane" => MemLaneInstruction::new(|memarg, lane| I::V128Load32Lane { memarg: memarg, lane: lane }, 2, 4), + "v128.load64_lane" => MemLaneInstruction::new(|memarg, lane| I::V128Load64Lane { memarg: memarg, lane: lane }, 3, 2), _ => return None, }; return Some(ins); @@ -477,10 +476,10 @@ impl Intrinsics { pub fn find_store_lane(&self, name: &str) -> Option { use enc::Instruction as I; let ins = match name { - "v128.store8_lane" => MemLaneInstruction::new(|memarg, lane| I::V128Store8Lane { memarg: memarg, lane: lane }, 0, 0), - "v128.store16_lane" => MemLaneInstruction::new(|memarg, lane| I::V128Store16Lane { memarg: memarg, lane: lane }, 1, 1), - "v128.store32_lane" => MemLaneInstruction::new(|memarg, lane| I::V128Store32Lane { memarg: memarg, lane: lane }, 2, 2), - "v128.store64_lane" => MemLaneInstruction::new(|memarg, lane| I::V128Store64Lane { memarg: memarg, lane: lane }, 3, 3), + "v128.store8_lane" => MemLaneInstruction::new(|memarg, lane| I::V128Store8Lane { memarg: memarg, lane: lane }, 0, 16), + "v128.store16_lane" => MemLaneInstruction::new(|memarg, lane| I::V128Store16Lane { memarg: memarg, lane: lane }, 1, 8), + "v128.store32_lane" => MemLaneInstruction::new(|memarg, lane| I::V128Store32Lane { memarg: memarg, lane: lane }, 2, 4), + "v128.store64_lane" => MemLaneInstruction::new(|memarg, lane| I::V128Store64Lane { memarg: memarg, lane: lane }, 3, 2), _ => return None, }; return Some(ins); @@ -490,20 +489,29 @@ impl Intrinsics { use enc::Instruction as I; use Type::*; let ins = match name { - "i8x16.extract_lane_s" => LaneInstruction::new(None, I32, |lane| I::I8x16ExtractLaneS { lane: lane }, 0), - "i8x16.extract_lane_u" => LaneInstruction::new(None, I32, |lane| I::I8x16ExtractLaneU { lane: lane }, 0), - "i8x16.replace_lane" => LaneInstruction::new(Some(I32), V128, |lane| I::I8x16ReplaceLane { lane: lane }, 0), - "i16x8.extract_lane_s" => LaneInstruction::new(None, I32, |lane| I::I16x8ExtractLaneS { lane: lane }, 1), - "i16x8.extract_lane_u" => LaneInstruction::new(None, I32, |lane| I::I16x8ExtractLaneU { lane: lane }, 1), - "i16x8.replace_lane" => LaneInstruction::new(Some(I32), V128, |lane| I::I16x8ReplaceLane { lane: lane }, 1), - "i32x4.extract_lane" => LaneInstruction::new(None, I32, |lane| I::I32x4ExtractLane { lane: lane }, 2), - "i32x4.replace_lane" => LaneInstruction::new(Some(I32), V128, |lane| I::I32x4ReplaceLane { lane: lane }, 2), - "i64x2.extract_lane" => LaneInstruction::new(None, I64, |lane| I::I64x2ExtractLane { lane: lane }, 3), - "i64x2.replace_lane" => LaneInstruction::new(Some(I64), V128, |lane| I::I64x2ReplaceLane { lane: lane }, 3), - "f32x4.extract_lane" => LaneInstruction::new(None, F64, |lane| I::F32x4ExtractLane { lane: lane }, 2), - "f32x4.replace_lane" => LaneInstruction::new(Some(F64), V128, |lane| I::F32x4ReplaceLane { lane: lane }, 2), - "f64x2.extract_lane" => LaneInstruction::new(None, F64, |lane| I::F64x2ExtractLane { lane: lane }, 3), - "f64x2.replace_lane" => LaneInstruction::new(Some(F64), V128, |lane| I::F64x2ReplaceLane { lane: lane }, 3), + "i8x16.extract_lane_s" => LaneInstruction::new(None, I32, |lane| I::I8x16ExtractLaneS { lane: lane }, 16), + "i8x16.extract_lane_u" => LaneInstruction::new(None, I32, |lane| I::I8x16ExtractLaneU { lane: lane }, 16), + "i8x16.replace_lane" => LaneInstruction::new(Some(I32), V128, |lane| I::I8x16ReplaceLane { lane: lane }, 16), + "i16x8.extract_lane_s" => LaneInstruction::new(None, I32, |lane| I::I16x8ExtractLaneS { lane: lane }, 8), + "i16x8.extract_lane_u" => LaneInstruction::new(None, I32, |lane| I::I16x8ExtractLaneU { lane: lane }, 8), + "i16x8.replace_lane" => LaneInstruction::new(Some(I32), V128, |lane| I::I16x8ReplaceLane { lane: lane }, 8), + "i32x4.extract_lane" => LaneInstruction::new(None, I32, |lane| I::I32x4ExtractLane { lane: lane }, 4), + "i32x4.replace_lane" => LaneInstruction::new(Some(I32), V128, |lane| I::I32x4ReplaceLane { lane: lane }, 4), + "i64x2.extract_lane" => LaneInstruction::new(None, I64, |lane| I::I64x2ExtractLane { lane: lane }, 2), + "i64x2.replace_lane" => LaneInstruction::new(Some(I64), V128, |lane| I::I64x2ReplaceLane { lane: lane }, 2), + "f32x4.extract_lane" => LaneInstruction::new(None, F64, |lane| I::F32x4ExtractLane { lane: lane }, 4), + "f32x4.replace_lane" => LaneInstruction::new(Some(F64), V128, |lane| I::F32x4ReplaceLane { lane: lane }, 4), + "f64x2.extract_lane" => LaneInstruction::new(None, F64, |lane| I::F64x2ExtractLane { lane: lane }, 2), + "f64x2.replace_lane" => LaneInstruction::new(Some(F64), V128, |lane| I::F64x2ReplaceLane { lane: lane }, 2), + _ => return None, + }; + return Some(ins); + } + + pub fn find_shuffle(&self, name: &str) -> Option { + use enc::Instruction as I; + let ins = match name { + "i8x16.shuffle" => ShuffleInstruction::new(|lanes| I::I8x16Shuffle { lanes: lanes }, 32), _ => return None, }; return Some(ins); @@ -533,19 +541,19 @@ impl MemInstruction { pub struct MemLaneInstruction { pub instruction: fn(MemArg, Lane) -> enc::Instruction<'static>, pub natural_alignment: u32, - pub lane_width: u32, + pub num_lanes: u32, } impl MemLaneInstruction { fn new( instruction: fn(MemArg, Lane) -> enc::Instruction<'static>, natural_alignment: u32, - lane_width: u32, + num_lanes: u32, ) -> MemLaneInstruction { MemLaneInstruction { instruction, natural_alignment, - lane_width + num_lanes } } } @@ -554,7 +562,7 @@ pub struct LaneInstruction { pub param_type: Option, pub return_type: Type, pub instruction: fn(Lane) -> enc::Instruction<'static>, - pub lane_width: u32, + pub num_lanes: u32, } impl LaneInstruction { @@ -562,13 +570,30 @@ impl LaneInstruction { param_type: Option, return_type: Type, instruction: fn(Lane) -> enc::Instruction<'static>, - lane_width: u32, + num_lanes: u32, ) -> LaneInstruction { LaneInstruction { param_type, return_type, instruction, - lane_width, + num_lanes, + } + } +} + +pub struct ShuffleInstruction { + pub instruction: fn([Lane; 16]) -> enc::Instruction<'static>, + pub num_lanes: u32, +} + +impl ShuffleInstruction { + fn new( + instruction: fn([Lane; 16]) -> enc::Instruction<'static>, + num_lanes: u32, + ) -> ShuffleInstruction { + ShuffleInstruction { + instruction, + num_lanes, } } } diff --git a/src/typecheck.rs b/src/typecheck.rs index b61baed..948bd8b 100644 --- a/src/typecheck.rs +++ b/src/typecheck.rs @@ -737,7 +737,7 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<() return report_error("Missing parameters", &expr.span, context.sources); } if let Some(value) = params.get_mut(1) { - tc_lane(load_lane.lane_width, context, value, &expr.span)?; + tc_lane(load_lane.num_lanes, context, value, &expr.span)?; } else { return report_error("Missing parameters", &expr.span, context.sources); } @@ -776,7 +776,7 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<() return report_error("Missing parameters", &expr.span, context.sources); } if let Some(value) = params.get_mut(1) { - tc_lane(store_lane.lane_width, context, value, &expr.span)?; + tc_lane(store_lane.num_lanes, context, value, &expr.span)?; } else { return report_error("Missing parameters", &expr.span, context.sources); } @@ -798,7 +798,7 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<() return report_error("Missing parameters", &expr.span, context.sources); } if let Some(value) = params.get_mut(1) { - tc_lane(lane_instr.lane_width, context, value, &expr.span)?; + tc_lane(lane_instr.num_lanes, context, value, &expr.span)?; } else { return report_error("Missing parameters", &expr.span, context.sources); } @@ -819,6 +819,28 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<() } } Some(lane_instr.return_type) + } else if let Some(_) = context.intrinsics.find_shuffle(name) { + for i in 0..2 { + if let Some(value) = params.get_mut(i) { + tc_expression(context, value)?; + if value.type_ != Some(V128) { + type_mismatch( + Some(V128), + &expr.span, + value.type_, + &value.span, + context.sources, + )?; + } + } else { + return report_error("Missing parameters", &expr.span, context.sources); + } + } + if params.len() > 18 { + return report_error("Too many parameters", &expr.span, context.sources); + } + (&mut params[2..]).into_iter().map(|p| tc_lane(32, context, p, &expr.span)).collect::>()?; + Some(V128) } else if let Some(type_map) = context .functions .get(name) @@ -1015,16 +1037,15 @@ fn tc_memarg(context: &mut Context, params: &mut [ast::Expression], span: &Span) Ok(()) } -fn tc_lane(lane_width: u32, context: &mut Context, param: &mut ast::Expression, span: &Span) -> Result<()> { +fn tc_lane(num_lanes: u32, context: &mut Context, param: &mut ast::Expression, span: &Span) -> Result<()> { if param.type_ != Some(I32) { return type_mismatch(Some(I32), &span, param.type_, ¶m.span, context.sources); } tc_const(param, context.sources)?; let lane = param.const_i32(); - let max_lane = (16 >> lane_width) - 1; - if lane < 0 || lane > max_lane { + if lane < 0 || (lane as u32) >= num_lanes { return report_error( - &format!("Lane {} out of range (0-{})", lane, max_lane), + &format!("Lane {} out of range (0-{})", lane, num_lanes-1), ¶m.span, context.sources, ); From 6110a027605fe475ba7d88d201fe0727156355d3 Mon Sep 17 00:00:00 2001 From: luchak Date: Mon, 13 Feb 2023 18:26:13 -0800 Subject: [PATCH 10/11] Fix some intrinsics data entry bugs --- src/intrinsics.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/intrinsics.rs b/src/intrinsics.rs index d42b18e..0b5aeee 100644 --- a/src/intrinsics.rs +++ b/src/intrinsics.rs @@ -189,13 +189,13 @@ impl Intrinsics { self.inst("i32x4.extadd_pairwise_i16x8_s", &[V128], Some(V128), I::I32x4ExtAddPairwiseI16x8S); self.inst("i32x4.extadd_pairwise_i16x8_u", &[V128], Some(V128), I::I32x4ExtAddPairwiseI16x8U); - self.inst("i8x16.add_sat_s", &[V128, V128], Some(V128), I::I16x8AddSatS); - self.inst("i8x16.add_sat_u", &[V128, V128], Some(V128), I::I16x8AddSatU); + self.inst("i8x16.add_sat_s", &[V128, V128], Some(V128), I::I8x16AddSatS); + self.inst("i8x16.add_sat_u", &[V128, V128], Some(V128), I::I8x16AddSatU); self.inst("i16x8.add_sat_s", &[V128, V128], Some(V128), I::I16x8AddSatS); self.inst("i16x8.add_sat_u", &[V128, V128], Some(V128), I::I16x8AddSatU); - self.inst("i8x16.sub_sat_s", &[V128, V128], Some(V128), I::I16x8SubSatS); - self.inst("i8x16.sub_sat_u", &[V128, V128], Some(V128), I::I16x8SubSatU); + self.inst("i8x16.sub_sat_s", &[V128, V128], Some(V128), I::I8x16SubSatS); + self.inst("i8x16.sub_sat_u", &[V128, V128], Some(V128), I::I8x16SubSatU); self.inst("i16x8.sub_sat_s", &[V128, V128], Some(V128), I::I16x8SubSatS); self.inst("i16x8.sub_sat_u", &[V128, V128], Some(V128), I::I16x8SubSatU); @@ -247,7 +247,7 @@ impl Intrinsics { self.inst("i64x2.shr_s", &[V128, I32], Some(V128), I::I64x2ShrS); self.inst("i64x2.shr_u", &[V128, I32], Some(V128), I::I64x2ShrU); - self.inst("v128.and", &[V128, V128], Some(V128), I::V128Not); + self.inst("v128.and", &[V128, V128], Some(V128), I::V128And); self.inst("v128.or", &[V128, V128], Some(V128), I::V128Or); self.inst("v128.xor", &[V128, V128], Some(V128), I::V128Xor); self.inst("v128.not", &[V128], Some(V128), I::V128Not); @@ -499,8 +499,8 @@ impl Intrinsics { "i32x4.replace_lane" => LaneInstruction::new(Some(I32), V128, |lane| I::I32x4ReplaceLane { lane: lane }, 4), "i64x2.extract_lane" => LaneInstruction::new(None, I64, |lane| I::I64x2ExtractLane { lane: lane }, 2), "i64x2.replace_lane" => LaneInstruction::new(Some(I64), V128, |lane| I::I64x2ReplaceLane { lane: lane }, 2), - "f32x4.extract_lane" => LaneInstruction::new(None, F64, |lane| I::F32x4ExtractLane { lane: lane }, 4), - "f32x4.replace_lane" => LaneInstruction::new(Some(F64), V128, |lane| I::F32x4ReplaceLane { lane: lane }, 4), + "f32x4.extract_lane" => LaneInstruction::new(None, F32, |lane| I::F32x4ExtractLane { lane: lane }, 4), + "f32x4.replace_lane" => LaneInstruction::new(Some(F32), V128, |lane| I::F32x4ReplaceLane { lane: lane }, 4), "f64x2.extract_lane" => LaneInstruction::new(None, F64, |lane| I::F64x2ExtractLane { lane: lane }, 2), "f64x2.replace_lane" => LaneInstruction::new(Some(F64), V128, |lane| I::F64x2ReplaceLane { lane: lane }, 2), _ => return None, From a552f9b3a6d74b606a0d1e7e003981193778d72e Mon Sep 17 00:00:00 2001 From: luchak Date: Tue, 14 Feb 2023 18:24:32 -0800 Subject: [PATCH 11/11] Make *.neg opcodes correctly take only one operand --- src/intrinsics.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/intrinsics.rs b/src/intrinsics.rs index 0b5aeee..931f3fd 100644 --- a/src/intrinsics.rs +++ b/src/intrinsics.rs @@ -164,12 +164,12 @@ impl Intrinsics { self.inst("i32x4.dot_i16x8_s", &[V128, V128], Some(V128), I::I32x4DotI16x8S); - self.inst("i8x16.neg", &[V128, V128], Some(V128), I::I8x16Neg); - self.inst("i16x8.neg", &[V128, V128], Some(V128), I::I16x8Neg); - self.inst("i32x4.neg", &[V128, V128], Some(V128), I::I32x4Neg); - self.inst("i64x2.neg", &[V128, V128], Some(V128), I::I64x2Neg); - self.inst("f32x4.neg", &[V128, V128], Some(V128), I::F32x4Neg); - self.inst("f64x2.neg", &[V128, V128], Some(V128), I::F64x2Neg); + self.inst("i8x16.neg", &[V128], Some(V128), I::I8x16Neg); + self.inst("i16x8.neg", &[V128], Some(V128), I::I16x8Neg); + self.inst("i32x4.neg", &[V128], Some(V128), I::I32x4Neg); + self.inst("i64x2.neg", &[V128], Some(V128), I::I64x2Neg); + self.inst("f32x4.neg", &[V128], Some(V128), I::F32x4Neg); + self.inst("f64x2.neg", &[V128], Some(V128), I::F64x2Neg); self.inst("i16x8.extmul_low_i8x16_s", &[V128, V128], Some(V128), I::I16x8ExtMulLowI8x16S); self.inst("i16x8.extmul_high_i8x16_s", &[V128, V128], Some(V128), I::I16x8ExtMulHighI8x16S);