Skip to content

Commit 3560edc

Browse files
olwangclaude
andcommitted
rsscript: batched matmul + integer/bit Tensor ops (ops D)
Adds bmm (broadcasting batch dims) and idiv/mod/shl/shr/and/or/xor + bitcast over I32-dtype tensors (f32-backed, exact <=2^24) as native kernels wired into both backends; identical rsscript_runtime::tensor_* kernels so VM<->compiled parity is bit-exact. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent b73e2b1 commit 3560edc

6 files changed

Lines changed: 538 additions & 0 deletions

File tree

crates/rsscript/src/reg_vm/mod.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2108,6 +2108,17 @@ enum RegIntrinsic {
21082108
TensorCastI32,
21092109
TensorCastBool,
21102110
TensorDtypeCode,
2111+
// bmm+int/bit (ops D)
2112+
TensorBmm,
2113+
TensorIdiv,
2114+
TensorMod,
2115+
TensorShl,
2116+
TensorShr,
2117+
TensorAnd,
2118+
TensorOr,
2119+
TensorXor,
2120+
TensorBitcastF32ToI32,
2121+
TensorBitcastI32ToF32,
21112122
TensorErrorMessage,
21122123
CharCompare,
21132124
CharFromCode,
@@ -5375,6 +5386,17 @@ fn qualified_intrinsic(namespace: &str, name: &str) -> Option<RegIntrinsic> {
53755386
("Tensor", "cast_i32") => Some(RegIntrinsic::TensorCastI32),
53765387
("Tensor", "cast_bool") => Some(RegIntrinsic::TensorCastBool),
53775388
("Tensor", "dtype_code") => Some(RegIntrinsic::TensorDtypeCode),
5389+
// bmm+int/bit (ops D)
5390+
("Tensor", "bmm") => Some(RegIntrinsic::TensorBmm),
5391+
("Tensor", "idiv") => Some(RegIntrinsic::TensorIdiv),
5392+
("Tensor", "modulo") => Some(RegIntrinsic::TensorMod),
5393+
("Tensor", "shl") => Some(RegIntrinsic::TensorShl),
5394+
("Tensor", "shr") => Some(RegIntrinsic::TensorShr),
5395+
("Tensor", "bit_and") => Some(RegIntrinsic::TensorAnd),
5396+
("Tensor", "bit_or") => Some(RegIntrinsic::TensorOr),
5397+
("Tensor", "bit_xor") => Some(RegIntrinsic::TensorXor),
5398+
("Tensor", "bitcast_f32_to_i32") => Some(RegIntrinsic::TensorBitcastF32ToI32),
5399+
("Tensor", "bitcast_i32_to_f32") => Some(RegIntrinsic::TensorBitcastI32ToF32),
53785400
("TensorError", "message") => Some(RegIntrinsic::TensorErrorMessage),
53795401
("Char", "compare") => Some(RegIntrinsic::CharCompare),
53805402
("Char", "from_code") => Some(RegIntrinsic::CharFromCode),
@@ -9468,6 +9490,44 @@ impl RegVm {
94689490
let t = self.expect_tensor_ref(intrinsic_arg(&self.stack, base, args, 0)?)?;
94699491
Ok(VmValue::Int(rsscript_runtime::tensor_dtype_code(&t)))
94709492
}
9493+
// bmm+int/bit (ops D)
9494+
RegIntrinsic::TensorBmm
9495+
| RegIntrinsic::TensorIdiv
9496+
| RegIntrinsic::TensorMod
9497+
| RegIntrinsic::TensorShl
9498+
| RegIntrinsic::TensorShr
9499+
| RegIntrinsic::TensorAnd
9500+
| RegIntrinsic::TensorOr
9501+
| RegIntrinsic::TensorXor => {
9502+
let a = self.expect_tensor_ref(intrinsic_arg(&self.stack, base, args, 0)?)?;
9503+
let b = self.expect_tensor_ref(intrinsic_arg(&self.stack, base, args, 1)?)?;
9504+
let result = match intrinsic {
9505+
RegIntrinsic::TensorBmm => rsscript_runtime::tensor_bmm(&a, &b),
9506+
RegIntrinsic::TensorIdiv => rsscript_runtime::tensor_idiv(&a, &b),
9507+
RegIntrinsic::TensorMod => rsscript_runtime::tensor_mod(&a, &b),
9508+
RegIntrinsic::TensorShl => rsscript_runtime::tensor_shl(&a, &b),
9509+
RegIntrinsic::TensorShr => rsscript_runtime::tensor_shr(&a, &b),
9510+
RegIntrinsic::TensorAnd => rsscript_runtime::tensor_and(&a, &b),
9511+
RegIntrinsic::TensorOr => rsscript_runtime::tensor_or(&a, &b),
9512+
_ => rsscript_runtime::tensor_xor(&a, &b),
9513+
};
9514+
Ok(json_result(match result {
9515+
Ok(tensor) => Ok(self.store_tensor(tensor)),
9516+
Err(error) => Err(tensor_error_value(
9517+
rsscript_runtime::tensor_error_message(&error),
9518+
)),
9519+
}))
9520+
}
9521+
RegIntrinsic::TensorBitcastF32ToI32 | RegIntrinsic::TensorBitcastI32ToF32 => {
9522+
let t = self.expect_tensor_ref(intrinsic_arg(&self.stack, base, args, 0)?)?;
9523+
let result = match intrinsic {
9524+
RegIntrinsic::TensorBitcastF32ToI32 => {
9525+
rsscript_runtime::tensor_bitcast_f32_to_i32(&t)
9526+
}
9527+
_ => rsscript_runtime::tensor_bitcast_i32_to_f32(&t),
9528+
};
9529+
Ok(self.store_tensor(result))
9530+
}
94719531
RegIntrinsic::CharCompare | RegIntrinsic::CharFromCode | RegIntrinsic::CharIsAlphanumeric | RegIntrinsic::CharIsAlpha | RegIntrinsic::CharIsDigit | RegIntrinsic::CharIsLower | RegIntrinsic::CharIsUpper | RegIntrinsic::CharIsWhitespace | RegIntrinsic::CharToCode | RegIntrinsic::CharToLower | RegIntrinsic::CharToString | RegIntrinsic::CharToUpper => self.exec_char_intrinsics(unit, intrinsic, args, base, next_base),
94729532
RegIntrinsic::ClockNow => Ok(instant_value(clock_system_unix_ms())),
94739533
RegIntrinsic::ClockSystemUnixMs => Ok(VmValue::Int(clock_system_unix_ms())),

crates/rsscript/src/runtime_abi.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,25 @@ const RUNTIME_INTRINSICS: &[RuntimeIntrinsic] = &[
460460
"dtype_code",
461461
"rsscript_runtime::tensor_dtype_code",
462462
),
463+
// bmm+int/bit (ops D)
464+
runtime_intrinsic("Tensor", "bmm", "rsscript_runtime::tensor_bmm"),
465+
runtime_intrinsic("Tensor", "idiv", "rsscript_runtime::tensor_idiv"),
466+
runtime_intrinsic("Tensor", "modulo", "rsscript_runtime::tensor_mod"),
467+
runtime_intrinsic("Tensor", "shl", "rsscript_runtime::tensor_shl"),
468+
runtime_intrinsic("Tensor", "shr", "rsscript_runtime::tensor_shr"),
469+
runtime_intrinsic("Tensor", "bit_and", "rsscript_runtime::tensor_and"),
470+
runtime_intrinsic("Tensor", "bit_or", "rsscript_runtime::tensor_or"),
471+
runtime_intrinsic("Tensor", "bit_xor", "rsscript_runtime::tensor_xor"),
472+
runtime_intrinsic(
473+
"Tensor",
474+
"bitcast_f32_to_i32",
475+
"rsscript_runtime::tensor_bitcast_f32_to_i32",
476+
),
477+
runtime_intrinsic(
478+
"Tensor",
479+
"bitcast_i32_to_f32",
480+
"rsscript_runtime::tensor_bitcast_i32_to_f32",
481+
),
463482
runtime_intrinsic(
464483
"TensorError",
465484
"message",

crates/rsscript/tests/vm_eval.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1073,6 +1073,7 @@ pub fn echo(message: &String) -> String {
10731073
// parity: runtime:Tensor.cmplt runtime:Tensor.cmpne runtime:Tensor.cmpeq runtime:Tensor.select
10741074
// parity: runtime:Tensor.maximum runtime:Tensor.minimum
10751075
// parity: runtime:Tensor.cast_f32 runtime:Tensor.cast_i32 runtime:Tensor.cast_bool runtime:Tensor.dtype_code
1076+
// parity: runtime:Tensor.bmm runtime:Tensor.idiv runtime:Tensor.modulo runtime:Tensor.shl runtime:Tensor.shr runtime:Tensor.bit_and runtime:Tensor.bit_or runtime:Tensor.bit_xor runtime:Tensor.bitcast_f32_to_i32 runtime:Tensor.bitcast_i32_to_f32
10761077
// parity: runtime:Timer.sleep runtime:Timer.sleep_cancellable runtime:Timer.sleep_until
10771078
// parity: runtime:Tcp.connect runtime:TcpError.message
10781079
// parity: runtime:TcpStream.read runtime:TcpStream.shutdown

crates/rsscript/tests/vm_eval_parity/tensor.rs

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -859,3 +859,96 @@ fn main() -> Result<Unit, TensorError> {
859859
source,
860860
);
861861
}
862+
863+
// bmm+int/bit (ops D)
864+
865+
#[test]
866+
fn parity_tensor_bmm() {
867+
let source = r#"
868+
features: native, local
869+
870+
fn dump(t: read Tensor) -> Result<Unit, TensorError> {
871+
let shape = Tensor.shape(tensor: read t)?
872+
let mut si = 0
873+
while si < List.len(list: read shape) {
874+
Log.write(message: read String.from_int(value: List.get(list: read shape, index: si)))
875+
si = si + 1
876+
}
877+
let values = Tensor.to_f32_slice(tensor: read t)?
878+
let mut index = 0
879+
while index < List.len(list: read values) {
880+
Log.write(message: read Float.to_string(value: read List.get(list: read values, index: index)))
881+
index = index + 1
882+
}
883+
return Ok(Unit)
884+
}
885+
886+
fn main() -> Result<Unit, TensorError> {
887+
let a = Tensor.from_f32_slice(data: read [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], shape: read [2, 2, 2])?
888+
let b = Tensor.from_f32_slice(data: read [1.0, 0.0, 0.0, 1.0, 2.0, 0.0, 0.0, 2.0], shape: read [2, 2, 2])?
889+
let c = Tensor.bmm(a: read a, b: read b)?
890+
dump(t: read c)?
891+
// Broadcast a single matrix over the batch dim.
892+
let big = Tensor.from_f32_slice(data: read [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0], shape: read [2, 2, 3])?
893+
let w = Tensor.from_f32_slice(data: read [1.0, 0.0, 0.0, 1.0, 1.0, 1.0], shape: read [1, 3, 2])?
894+
let bc = Tensor.bmm(a: read big, b: read w)?
895+
dump(t: read bc)?
896+
return Ok(Unit)
897+
}
898+
"#;
899+
common::assert_vm_eval_matches_backend(
900+
"parity-tensor-bmm.rss",
901+
"rsscript_parity_tensor_bmm",
902+
source,
903+
);
904+
}
905+
906+
#[test]
907+
fn parity_tensor_int_bit_ops() {
908+
let source = r#"
909+
features: native, local
910+
911+
fn dump(t: read Tensor) -> Result<Unit, TensorError> {
912+
Log.write(message: read String.from_int(value: Tensor.dtype_code(t: read t)))
913+
let values = Tensor.to_f32_slice(tensor: read t)?
914+
let mut index = 0
915+
while index < List.len(list: read values) {
916+
Log.write(message: read Float.to_string(value: read List.get(list: read values, index: index)))
917+
index = index + 1
918+
}
919+
return Ok(Unit)
920+
}
921+
922+
fn main() -> Result<Unit, TensorError> {
923+
let af = Tensor.from_f32_slice(data: read [7.0, -7.0, 12.0, 6.0], shape: read [4])?
924+
let bf = Tensor.from_f32_slice(data: read [2.0, 2.0, 3.0, 0.0], shape: read [4])?
925+
let a = Tensor.cast_i32(t: read af)
926+
let b = Tensor.cast_i32(t: read bf)
927+
let q = Tensor.idiv(a: read a, b: read b)?
928+
dump(t: read q)?
929+
let r = Tensor.modulo(a: read a, b: read b)?
930+
dump(t: read r)?
931+
let shf = Tensor.from_f32_slice(data: read [1.0, 2.0, 1.0, 2.0], shape: read [4])?
932+
let sh = Tensor.cast_i32(t: read shf)
933+
let l = Tensor.shl(a: read a, b: read sh)?
934+
dump(t: read l)?
935+
let rs = Tensor.shr(a: read a, b: read sh)?
936+
dump(t: read rs)?
937+
let aa = Tensor.bit_and(a: read a, b: read b)?
938+
dump(t: read aa)?
939+
let oo = Tensor.bit_or(a: read a, b: read b)?
940+
dump(t: read oo)?
941+
let xx = Tensor.bit_xor(a: read a, b: read b)?
942+
dump(t: read xx)?
943+
let bits = Tensor.bitcast_f32_to_i32(t: read af)
944+
let back = Tensor.bitcast_i32_to_f32(t: read bits)
945+
dump(t: read back)?
946+
return Ok(Unit)
947+
}
948+
"#;
949+
common::assert_vm_eval_matches_backend(
950+
"parity-tensor-int-bit-ops.rss",
951+
"rsscript_parity_tensor_int_bit_ops",
952+
source,
953+
);
954+
}

0 commit comments

Comments
 (0)