Skip to content

Commit bfc542d

Browse files
dkorpelclaude
andcommitted
Add basic CI for -marm codegen using qemu
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent f588d11 commit bfc542d

9 files changed

Lines changed: 199 additions & 36 deletions

File tree

.github/workflows/main.yml

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,3 +281,52 @@ jobs:
281281
- name: Backup
282282
if: ${{ failure() }}
283283
run: echo 'FreeBSD VM check made optional because of random time outs'
284+
285+
arm-qemu:
286+
name: 'Ubuntu 24.04 x64, DMD (AArch64 cross-compile via QEMU)'
287+
runs-on: ubuntu-latest
288+
timeout-minutes: 30
289+
env:
290+
OS_NAME: linux
291+
MODEL: 64
292+
HOST_DMD: dmd
293+
FULL_BUILD: true
294+
defaults:
295+
run:
296+
shell: bash
297+
steps:
298+
- uses: actions/checkout@v4
299+
with:
300+
fetch-depth: 50
301+
302+
- name: Set environment variable N (parallelism)
303+
run: echo "N=$(nproc)" >> $GITHUB_ENV
304+
305+
- name: Install prerequisites
306+
run: sudo -E ci/cirrusci.sh
307+
308+
- name: Install host compiler
309+
run: ci/run.sh install_host_compiler
310+
311+
- name: Set up repos
312+
run: |
313+
set -uexo pipefail
314+
ref='${{ github.ref }}'
315+
if [[ "$ref" =~ ^refs/pull/ ]]; then
316+
REPO_BRANCH="$GITHUB_BASE_REF"
317+
elif [[ "$ref" =~ ^refs/(heads|tags)/(.*)$ ]]; then
318+
REPO_BRANCH="${BASH_REMATCH[2]}"
319+
else
320+
echo "Error: unexpected GitHub ref '$ref'" >&2
321+
exit 1
322+
fi
323+
ci/run.sh setup_repos "$REPO_BRANCH"
324+
325+
- name: Build DMD
326+
run: ci/run.sh build
327+
328+
- name: Install AArch64 cross-compile tools
329+
run: sudo apt-get install -y qemu-user clang lld gcc-aarch64-linux-gnu
330+
331+
- name: Test AArch64 cross-compile and run via QEMU
332+
run: compiler/test/run.d arm

compiler/src/dmd/backend/arm/cod2.d

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2228,7 +2228,7 @@ static if (0)
22282228
goto L4;
22292229

22302230
case FL.extern_:
2231-
if (config.exe & EX_posix)
2231+
if (config.exe & EX_posix && (e.Vsym.ty() & mTYthread))
22322232
{
22332233
if (log) printf("posix extern threaded\n");
22342234
regm_t scratch = INSTR.ALLREGS & ~mask(reg);

compiler/src/dmd/backend/arm/cod3.d

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2329,7 +2329,7 @@ uint codout(int seg, code* c, Barray!ubyte* disasmBuf, ref targ_size_t framehand
23292329
case SC.inline:
23302330
ggen.flush();
23312331
ggen.gen32(op);
2332-
objmod.reftoident(ggen.seg,ggen.offset,s,op,flags);
2332+
objmod.reftoident(ggen.seg,ggen.offset,s,c.IEV1.Voffset,flags);
23332333
break;
23342334

23352335
default:

compiler/src/dmd/backend/x86/cgcod.d

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -658,7 +658,7 @@ void prolog(ref CGstate cg, ref CodeBuilder cdb)
658658
!(config.exe == EX_WIN32) && !(funcsym_p.Sfunc.Fflags3 & Fnothrow) ||
659659
cg.accessedTLS ||
660660
sv64 ||
661-
(0 && cg.calledafunc && cg.AArch64)
661+
(cg.calledafunc && cg.AArch64)
662662
)
663663
{
664664
//printf("0 prolog() needframe %d alwaysframe %d\n", cg.needframe, config.flags & CFGalwaysframe);

compiler/test/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,11 @@ need to be started.
160160
quick: run all tests with no default permuted args
161161
(individual test specified options still honored)
162162

163+
arm: cross-compile AArch64 runnable tests (arm.d, ai.d) and run
164+
them under qemu-aarch64; requires clang, ld.lld, qemu-aarch64,
165+
and an AArch64 sysroot for clang (e.g. gcc-aarch64-linux-gnu);
166+
skipped automatically if any prerequisite is missing
167+
163168
clean: remove all temporary or result files from previous runs
164169

165170
test_results/compilable/json.d.out runs an individual test

compiler/test/dshell/arm_cross.d

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
#!/usr/bin/env rdmd
2+
/**
3+
Cross-compile AArch64 runnable tests and execute them under qemu-aarch64.
4+
5+
Can be run directly or via test runner:
6+
```
7+
rdmd compiler/test/dshell/arm_cross.d
8+
compiler/test/run.d arm
9+
```
10+
Or via the test runner:
11+
Install prerequisites:
12+
13+
Debian/Ubuntu:
14+
```
15+
sudo apt install qemu-user clang lld gcc-aarch64-linux-gnu
16+
```
17+
18+
Arch Linux:
19+
```
20+
sudo pacman -S qemu-user clang lld aarch64-linux-gnu-gcc
21+
```
22+
*/
23+
module arm_cross;
24+
25+
import std.array : join;
26+
import std.file : exists, mkdirRecurse, remove, tempDir;
27+
import std.file : fileWrite = write;
28+
import std.path;
29+
import std.process;
30+
import std.stdio;
31+
32+
// When run via run.d the DMD env var is set; otherwise fall back to the built binary.
33+
string dmd()
34+
{
35+
import tools.paths : dmdPath;
36+
37+
auto env = environment.get("DMD");
38+
return env ? env : dmdPath;
39+
}
40+
41+
int main()
42+
{
43+
foreach (tool; ["qemu-aarch64", "clang", "ld.lld"])
44+
{
45+
if (!toolExists(tool))
46+
{
47+
writeln("Skipping arm_cross: '", tool, "' not found in PATH");
48+
return 0;
49+
}
50+
}
51+
52+
immutable scriptDir = __FILE_FULL_PATH__.dirName;
53+
immutable testDir = scriptDir.buildPath("..", "runnable");
54+
immutable drImport = scriptDir.buildPath("..", "..", "druntime", "import");
55+
immutable outDir = tempDir.buildPath("arm_cross_tests");
56+
57+
if (!outDir.exists)
58+
outDir.mkdirRecurse;
59+
60+
int result = 0;
61+
foreach (testName; [
62+
"ai",
63+
"arm",
64+
"bcraii",
65+
"bcraii2",
66+
"opcolon",
67+
"powinline",
68+
"test18472",
69+
"test21416",
70+
"test24884",
71+
])
72+
result |= runTest(outDir, testDir, drImport, testName);
73+
return result;
74+
}
75+
76+
int runTest(string outDir, string testDir, string drImport, string testName)
77+
{
78+
immutable armO = buildPath(outDir, testName ~ ".o");
79+
immutable armExe = buildPath(outDir, testName);
80+
immutable armSrc = buildPath(testDir, testName ~ ".d");
81+
82+
writefln("--- %s (AArch64) ---", testName);
83+
84+
if (run([
85+
dmd, "-marm64", "-betterC", "-c", armSrc, "-I" ~ drImport,
86+
"-of=" ~ armO
87+
]) != 0)
88+
return 1;
89+
90+
if (run([
91+
"clang", "--target=aarch64-linux-gnu", "-fuse-ld=lld", "-static",
92+
armO, "-o", armExe
93+
]) != 0)
94+
return 1;
95+
96+
return run(["qemu-aarch64", armExe]);
97+
}
98+
99+
int run(string[] args)
100+
{
101+
writeln("+ ", args.join(" "));
102+
stdout.flush();
103+
return spawnProcess(args).wait;
104+
}
105+
106+
bool toolExists(string program)
107+
{
108+
return execute(["which", program]).status == 0;
109+
}

compiler/test/run.d

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,11 @@ Target[] predefinedTargets(string[] targets)
448448
newTargets.put(findFiles("dshell").map!createTestTarget);
449449
break;
450450

451+
case "arm":
452+
newTargets ~= Target("",
453+
["rdmd", "-I" ~ toolsDir, testPath(buildPath("dshell", "arm_cross.d"))]);
454+
break;
455+
451456
case "all":
452457
version (FreeBSD) { /* ??? unittest runner fails for no good reason on GHA. */ }
453458
else

compiler/test/runnable/ai.d

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Test cases generated by AI.
55

66
import core.stdc.stdio;
77

8-
void arithmetic_expression_selftest()
8+
int arithmetic_expression_selftest()
99
{
1010
/* If a bit is set in `fail`, that test expression evaluated differently than expected. */
1111
uint fail = 0;
@@ -106,11 +106,11 @@ void arithmetic_expression_selftest()
106106

107107
if (fail)
108108
printf("fail: x%x\n", fail);
109-
assert(!fail);
109+
110+
return fail;
110111
}
111112

112-
int main()
113+
extern(C) int main()
113114
{
114-
arithmetic_expression_selftest();
115-
return 0;
115+
return arithmetic_expression_selftest();
116116
}

compiler/test/runnable/arm.d

Lines changed: 23 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,6 @@
44
* need to pass the x86 compiler.
55
*/
66

7-
import core.stdc.stdio;
8-
9-
/******************************************************/
10-
117
pragma(inline, false)
128
{
139
ulong movregconst64_0() { return 0x123456789abcdef0; }
@@ -30,34 +26,33 @@ uint movregconst32_4() { return 0xAAAAAAAA; }
3026
uint movregconst32_5() { return 0xFFFFFFFF; }
3127
}
3228

33-
void testmovregconst()
29+
// Returns nonzero if any value is wrong
30+
int testmovregconst()
3431
{
35-
assert(movregconst64_0() == 0x123456789abcdef0);
36-
assert(movregconst64_1() == 0x12345678);
37-
assert(movregconst64_2() == 0xFFFF5678);
38-
assert(movregconst64_3() == 0x1234FFFF);
39-
assert(movregconst64_4() == 0xFFFF_FFFF_FFFF_1234);
40-
assert(movregconst64_5() == 0xFFFF_FFFF_1234_FFFF);
41-
assert(movregconst64_6() == 0xFFFF_1234_FFFF_FFFF);
42-
assert(movregconst64_7() == 0x1234_FFFF_FFFF_FFFF);
43-
assert(movregconst64_8() == 0x5555_5555_5555_5555);
44-
assert(movregconst64_9() == 0xAAAA_AAAA_AAAA_AAAA);
45-
assert(movregconst64_10() == 0xFFFF_FFFF_FFFF_FFFE);
46-
47-
assert(movregconst32_0() == 0x12345678);
48-
assert(movregconst32_1() == 0xFFFF5678);
49-
assert(movregconst32_2() == 0x1234FFFF);
50-
assert(movregconst32_3() == 0x55555555);
51-
assert(movregconst32_4() == 0xAAAAAAAA);
52-
assert(movregconst32_5() == 0xFFFFFFFF);
32+
int r;
33+
r |= movregconst64_0() != 0x123456789abcdef0;
34+
r |= movregconst64_1() != 0x12345678;
35+
r |= movregconst64_2() != 0xFFFF5678;
36+
r |= movregconst64_3() != 0x1234FFFF;
37+
r |= movregconst64_4() != 0xFFFF_FFFF_FFFF_1234;
38+
r |= movregconst64_5() != 0xFFFF_FFFF_1234_FFFF;
39+
r |= movregconst64_6() != 0xFFFF_1234_FFFF_FFFF;
40+
r |= movregconst64_7() != 0x1234_FFFF_FFFF_FFFF;
41+
r |= movregconst64_8() != 0x5555_5555_5555_5555;
42+
r |= movregconst64_9() != 0xAAAA_AAAA_AAAA_AAAA;
43+
r |= movregconst64_10() != 0xFFFF_FFFF_FFFF_FFFE;
44+
r |= movregconst32_0() != 0x12345678;
45+
r |= movregconst32_1() != 0xFFFF5678;
46+
r |= movregconst32_2() != 0x1234FFFF;
47+
r |= movregconst32_3() != 0x55555555;
48+
r |= movregconst32_4() != 0xAAAAAAAA;
49+
r |= movregconst32_5() != 0xFFFFFFFF;
50+
return r;
5351
}
5452

5553
/******************************************************/
5654

57-
int main()
55+
extern(C) int main()
5856
{
59-
testmovregconst();
60-
61-
printf("Success\n");
62-
return 0;
57+
return testmovregconst();
6358
}

0 commit comments

Comments
 (0)