Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Get WebGPU integration testing working again #958

Merged
merged 3 commits into from
Nov 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ jobs:
runs-on: [self-hosted, macOS]
steps:
- uses: actions/checkout@v4
- name: Node deps
run: yarn
- name: Start web server
run: yarn start-server
- name: Build
run: cargo build --verbose
- if: ${{ github.ref_name == 'main' }}
Expand All @@ -50,6 +54,9 @@ jobs:
- if: ${{ github.ref_name != 'main' }}
name: Run tests
run: cargo test --verbose
- if: always()
name: Stop web server
run: yarn stop-server

test-arm-linux:
runs-on: [self-hosted, linux, ARM64]
Expand Down
23 changes: 5 additions & 18 deletions alan/src/compile/integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,16 +228,11 @@ macro_rules! test_gpgpu {
// My playwright scripts only work on Linux and MacOS, though, so that reduces it
// to just MacOS to test this on.
// if cfg!(windows) || cfg!(macos) {
// TODO: This apparently wasn't working at all because the `macos` cfg keyword was
// deprecated at some point and the new version of Rust finally told me? In any
// case, fixing this will be in a follow-up PR
/*
if cfg!(target_os = "macos") {
crate::program::Program::set_target_lang_js();
{
let mut program = crate::program::Program::get_program().lock().unwrap();
program.env.insert("ALAN_TARGET".to_string(), "test".to_string());
}
let mut program = crate::program::Program::get_program();
program.env.insert("ALAN_TARGET".to_string(), "test".to_string());
crate::program::Program::return_program(program);
match crate::compile::web(filename.to_string()) {
Ok(_) => { /* Do nothing */ }
Err(e) => {
Expand Down Expand Up @@ -278,21 +273,14 @@ macro_rules! test_gpgpu {
return Err(format!("Failed to create temporary HTML file {:?}", e).into());
}
};
match std::process::Command::new("bash").arg("-c").arg("yarn start-server").output() {
Ok(a) => Ok(a),
Err(e) => Err(format!("Could not start test web server {:?}", e)),
}?;
// We're assuming an HTTP server is already running
let run = match std::process::Command::new("bash")
.arg("-c")
.arg(format!("yarn chrome-console http://localhost:8080/alan/{}.html", stringify!($rule)))
.arg(format!("yarn -s chrome-console http://localhost:8080/alan/{}.html", stringify!($rule)))
.output() {
Ok(a) => Ok(a),
Err(e) => Err(format!("Could not run the test JS code {:?}", e)),
}?;
match std::process::Command::new("bash").arg("-c").arg("yarn stop-server").output() {
Ok(a) => Ok(a),
Err(e) => Err(format!("Could not start test web server {:?}", e)),
}?;
$( $type!($test_val, false, &run); )+
match std::fs::remove_file(&jsfile) {
Ok(a) => Ok(a),
Expand All @@ -303,7 +291,6 @@ macro_rules! test_gpgpu {
Err(e) => Err(format!("Could not remove the generated HTML file {:?}", e)),
}?;
}
*/
std::fs::remove_file(&filename)?;
Ok(())
}
Expand Down
24 changes: 18 additions & 6 deletions alan/src/std/root.ln
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ export const tau = 6.283185307179586;
/// Functions for (potentially) every type
export fn{Rs} clone{T} (v: T) -> T = {Method{"clone"} :: T -> T}(v);
// TODO: This needs to be turned into a JS function that's bound
export fn{Js} clone{T} (v: T) -> T = {"(function clone(t) { if (t instanceof Array) { return t.map(clone); } else if (t.build instanceof Function) { return t.build(t.val); } else { return structuredClone(t) } })" :: T -> T}(v);
export fn{Js} clone{T} (v: T) -> T = {"(function clone(t) { if (t instanceof Array) { return t.map(clone); } else if (t.build instanceof Function) { return t.build(t.val); } else if (t instanceof Set) { return t.union(new Set()); } else { return structuredClone(t); } })" :: T -> T}(v);
// TODO: The "proper" way to hash this consistently for all types is to decompose the input type
// into the various primitive types of Alan and then have hashing rules for each of them, which
// may themselves decompose, etc. This might be doable in Alan code on top of specialized hashing
Expand Down Expand Up @@ -1110,7 +1110,8 @@ export fn{Rs} repeat (a: string, n: i64) = {Method{"repeat"} :: (string, Deref{B
a,
{Cast{"usize"} :: Deref{i64} -> Binds{"usize"}}(n));
export fn{Js} repeat "((s, n) => new alan_std.Str(s.val.repeat(Number(n.val))))" <- RootBacking :: (string, i64) -> string;
export fn replace Method{"replace"} :: (string, string, string) -> string;
export fn{Rs} replace Method{"replace"} :: (string, string, string) -> string;
export fn{Js} replace "((s, o, n) => new alan_std.Str(s.valueOf().replaceAll(o.valueOf(), n.valueOf())))" :: (string, string, string) -> string;
export fn{Rs} split "alan_std::splitstring" <- RootBacking :: (string, string) -> string[];
export fn{Js} split "((a, b) => a.val.split(b.val).map(v => new alan_std.Str(v)))" <- RootBacking :: (string, string) -> string[];
export fn{Rs} len (s: string) = {Cast{"i64"} :: Deref{Binds{"usize"}} -> i64}(
Expand Down Expand Up @@ -1176,6 +1177,7 @@ export fn{Js} concat{T} (a: T[], b: T[]) -> T[] = {Method{"concat"} :: (T[], T[]
export fn{Rs} append{T} "alan_std::append" <- RootBacking :: (Mut{T[]}, T[]);
export fn{Js} append{T} "((a, b) => { for (let v in b) { a.push(v); } })" :: (Mut{T[]}, T[]);
export fn{Rs} filled{T} "alan_std::filled" <- RootBacking :: (T, i64) -> T[];
export fn{Js} filled{T} "((v, c) => { let out = []; for (let i = 0n; i < c; i++) { out.push(v); } return out; })" :: (T, i64) -> T[];
export fn{Rs} has{T} (a: T[], v: T) = {Method{"contains"} :: (T[], T) -> bool}(a, v);
export fn{Js} has{T} (a: T[], v: T) = a.reduce(false, fn (out: bool, t: T) = if(out, true, t == v));
export fn{Rs} has{T} "alan_std::hasfnarray" <- RootBacking :: (T[], T -> bool) -> bool;
Expand Down Expand Up @@ -1474,7 +1476,7 @@ export fn{Rs} f64 Method{"as_secs_f64"} :: Duration -> f64;

/// Uuid-related bindings
export fn{Rs} uuid "alan_std::Uuid::new_v4" <- RootBacking :: () -> uuid;
export fn{Js} uuid "alan_std::uuidv4" <- RootBacking :: () -> uuid;
export fn{Js} uuid "alan_std.uuidv4" <- RootBacking :: () -> uuid;
export fn{Rs} string "format!" :: ("{}", uuid) -> string;
export fn{Js} string "new alan_std.Str" :: uuid -> string;

Expand All @@ -1495,18 +1497,18 @@ export fn{Js} mapWriteBuffer "alan_std.mapWriteBufferType" <- RootBacking :: ()
export fn{Rs} storageBuffer "alan_std::storage_buffer_type" <- RootBacking :: () -> BufferUsages;
export fn{Js} storageBuffer "alan_std.storageBufferType" <- RootBacking :: () -> BufferUsages;
export fn{Rs} GBuffer{T}(bu: BufferUsages, arr: T[]) = {"alan_std::create_buffer_init" <- RootBacking :: (BufferUsages, T[], i8) -> GBuffer}(bu, arr, {Size{T}}().i8);
export fn{Js} GBuffer{T}(bu: BufferUsages, arr: T[]) = {"alan_std.createBufferInit" <- RootBacking :: (BufferUsages, T[], i8) -> GBuffer}(bu, arr, {Size{T}}().i8);
export fn{Js} GBuffer{T}(bu: BufferUsages, arr: T[]) = {"alan_std.createBufferInit" <- RootBacking :: (BufferUsages, T[]) -> GBuffer}(bu, arr);
export fn{Rs} GBuffer{T}(bu: BufferUsages, size: i64) = {"alan_std::create_empty_buffer" <- RootBacking :: (BufferUsages, i64, i8) -> GBuffer}(bu, size, {Size{T}}().i8);
// TODO: Get the type into JS
export fn{Js} GBuffer{T}(bu: BufferUsages, size: i64) = {"alan_std.createEmptyBuffer" <- RootBacking :: (BufferUsages, i64) -> GBuffer}(bu, size);
export fn{Js} GBuffer{T}(bu: BufferUsages, size: i64) = {"alan_std.createEmptyBuffer" <- RootBacking :: (BufferUsages, i32) -> GBuffer}(bu, size.i32);
export fn GBuffer{T}(vals: T[]) = GBuffer{T}(storageBuffer(), vals);
export fn GBuffer{T}(size: i64) = GBuffer{T}(storageBuffer(), size);
export fn{Rs} len "alan_std::bufferlen" <- RootBacking :: GBuffer -> i64;
export fn{Js} len "alan_std.bufferlen" <- RootBacking :: GBuffer -> i64;
export fn{Rs} id "alan_std::buffer_id" <- RootBacking :: GBuffer -> string;
export fn{Js} id "alan_std.bufferid" <- RootBacking :: GBuffer -> string;
export fn{Rs} GPGPU "alan_std::GPGPU::new" <- RootBacking :: (Own{string}, Own{Array{Array{GBuffer}}}, Deref{i64[3]}) -> GPGPU;
export fn{Js} GPGPU "new alan_std.GPGPU" <- RootBacking :: (string, Array{Array{GBuffer}}, i64[3]) -> GPGPU;
export fn{Js} GPGPU (src: string, gbuffers: Array{Array{GBuffer}}, idx: i64[3]) = {"new alan_std.GPGPU" <- RootBacking :: (string, Array{Array{GBuffer}}, i32[3]) -> GPGPU}(src, gbuffers, idx.map(i32));
export fn GPGPU(src: string, buf: GBuffer) -> GPGPU {
// In order to support larger arrays, we need to split the buffer length across them. Each of
// indices is allowed to be up to 65535 (yes, a 16-bit integer) leading to a maximum length of
Expand Down Expand Up @@ -4793,6 +4795,15 @@ export fn map(gb: GBuffer, f: gf32 -> gf32) {
compute.build.run;
return out;
}
export fn{Js} map(gb: GBuffer, f: gf32 -> gf32) { // TODO: Get rid of this jank
let idx = gFor(gb.len);
let val = gb[idx].asF32;
let out = GBuffer{f32}(gb.len); // The JS binding currently ignores this
{"((b) => { b.ValKind = alan_std.F32; })" :: GBuffer}(out); // Trick to get it to work for now
let compute = out[idx].store(f(val).asI32);
compute.build.run;
return out;
}
// This one technically only works for i32/u32/f32 by chance. Once the issue above is fixed, this
// one can be fixed, too.
export fn map{G, G2}(gb: GBuffer, f: (G, gu32) -> G2) {
Expand All @@ -4819,6 +4830,7 @@ export fn ExitCode(e: i64) = ExitCode(e.u8);
// TODO: Rework this to just print anything that can be converted to `string` via interfaces
export fn{Rs} print{T}(v: T) = {"println!" :: ("{}", string)}(v.string);
export fn{Js} print{T}(v: T) = {"console.log" :: T}(v);
export fn{Js} print{T}(v: T[]) = {"((vs) => console.log(vs.map((v) => v.valueOf())))" :: T[]}(v);
export fn{Js} print "((s) => console.log(s.val))" :: string;
export fn{Js} print (b: bool) = b.string.print;
export fn{Js} print (i: i8) = i.string.print;
Expand Down
2 changes: 1 addition & 1 deletion chrome_console.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { chromium } from 'playwright';
const page = await context.newPage();
page.on('console', msg => console.log(msg.text()));
await page.goto(process.argv.pop());
await new Promise((r) => setTimeout(r, 1000)); // TODO: Better way to determine completion
await new Promise((r) => setTimeout(r, 5000)); // TODO: Better way to determine completion
await context.close();
await browser.close();
})();
Loading