From e184e7478157385705284637b6177fbe374b7628 Mon Sep 17 00:00:00 2001 From: Guilherme Lampert Date: Fri, 16 Oct 2026 16:01:35 +1100 Subject: [PATCH] Accept clipw's second operand without a field, as SCE VCL does CLIP/CLIPw/CLIPLw read only the W component of their second operand. VCLSML, SCEA's standard macro library for VCL, uses the bare form `clipw.xyz a, b` in its PerformClipCheck macro, and 9 of the Tyra engine's 15 VU1 programs use that macro: https://github.com/h4570/tyra/blob/c59f406af134713b62656114846ac8d588c6247c/engine/src/renderer/3d/pipeline/shared/vcl_sml.i#L1393-L1398 Testing with SCE's vcl 1.4beta7 (the binary Tyra builds with) confirms that it compiles the bare form to the same instruction as `clipw.xyz a, b[w]` (`clipw.xyz VF01xyz,VF01w`), and that it also accepts `clipw a, b` as `.xyz`. openvcl required the explicit [w] and failed with "Invalid argument", so those 9 programs did not compile. The 'wcomp' template modifier now defaults a missing field to W, so the bare form emits exactly what [w] emits. Any other field is still rejected, as vcl 1.4beta7 ("instruction and operand have mismatching field select") and dvp-as ("bad instruction") both reject it. Co-Authored-By: Claude Opus 5.5 --- src/Token.cpp | 15 +++-- src/Token.h | 3 +- test/CMakeLists.txt | 1 + test/integration/test_clip_implicit_w.cpp | 69 +++++++++++++++++++++++ 4 files changed, 83 insertions(+), 5 deletions(-) create mode 100644 test/integration/test_clip_implicit_w.cpp diff --git a/src/Token.cpp b/src/Token.cpp index 586585b..c3a3524 100644 --- a/src/Token.cpp +++ b/src/Token.cpp @@ -330,6 +330,9 @@ bool Token::processOperand( const Operand* operand, bool newSyntax, Token::Argum { if( 1 != countFields( fields ) ) match = false; + + if( hasModifier( WCOMP, modifiers ) && (W != fields) ) + match = false; } else if( hasModifier( DEST, modifiers ) ) { @@ -367,9 +370,12 @@ bool Token::processOperand( const Operand* operand, bool newSyntax, Token::Argum (*i).setFields( fields ); - // if flag hasn't been specified, fail + // if flag hasn't been specified, fail (a W component + // defaults to W instead) - if( hasModifier( FLAG, modifiers ) ) + if( hasModifier( WCOMP, modifiers ) ) + (*i).setFields( W ); + else if( hasModifier( FLAG, modifiers ) ) match = false; if( hasModifier( BROADCAST, modifiers ) && !activeBroadcast ) @@ -547,8 +553,9 @@ unsigned int Token::extractModifiers( const std::string& list ) else if( name == "bc" ) modifiers |= (1< +#include + +namespace +{ + std::string program(const std::string& clip) + { + return + "\t.init_vf_all\n" + "\t.init_vi_all\n" + "\t.syntax new\n" + "\t.name vsmClipImplicitW\n" + "\t--enter\n" + "\t--endenter\n" + "\tlq fA, 1(vi00)\n" + "\t" + clip + "\n" + "\tfcand vi01, 0x3f\n" + "\tisw.x vi01, 2(vi00)\n" + "\t--exit\n" + "\t--endexit\n"; + } + + ::test::RunResult compile(const std::string& clip) + { + std::vector args; + args.push_back("-o"); + args.push_back("-"); + return ::test::run_openvcl(args, program(clip)); + } +} + +TEST_CASE("openvcl: clipw without a field reads the W component") +{ + ::test::RunResult bare = compile("clipw.xyz fA, fA"); + ::test::RunResult explicitW = compile("clipw.xyz fA, fA[w]"); + + CHECK(bare.exit_code == 0); + CHECK(explicitW.exit_code == 0); + CHECK(bare.stdout_data.find("clipw.xyz VF01, VF01w") != std::string::npos); + CHECK(bare.stdout_data == explicitW.stdout_data); +} + +TEST_CASE("openvcl: clipw without a dest field or a source field is .xyz and W") +{ + ::test::RunResult bare = compile("clipw fA, fA"); + + CHECK(bare.exit_code == 0); + CHECK(bare.stdout_data.find("clipw.xyz VF01, VF01w") != std::string::npos); +} + +TEST_CASE("openvcl: clipw rejects a source field other than W") +{ + ::test::RunResult r = compile("clipw.xyz fA, fA[x]"); + + CHECK(r.exit_code != 0); + CHECK(r.stderr_data.find("Invalid argument") != std::string::npos); +}