android target + APK pipeline; LSP imports honor stdlib paths

Android (toolchain):
  --target android / --target android-arm64 → aarch64-linux-android21.
  target.zig discovers $ANDROID_NDK_HOME (or scans
  ~/Library/Android/sdk/ndk/* for the newest), invokes the NDK clang
  with -shared -fPIC and links libsxhello.so against -llog -landroid
  -lEGL -lGLESv3 -lm -ldl. native_app_glue.c from the NDK is compiled
  and linked alongside the sx .o so apps can use the conventional
  android_main(struct android_app*) shape; -u ANativeActivity_onCreate
  keeps glue's symbol live.

Android (APK):
  --apk <out> wraps the .so into a debug-signed installable APK.
  target.zig discovers the SDK at $ANDROID_HOME (or
  ~/Library/Android/sdk), picks the newest build-tools + platforms,
  generates a NativeActivity AndroidManifest.xml from --bundle-id,
  packages via aapt2 link, appends the lib/ tree, zipalign, then
  apksigner against ~/.android/debug.keystore (auto-generated via
  keytool on first use). One command end-to-end:
      sx build --target android --apk out.apk \\
          --bundle-id co.swipelab.foo main.sx
  Verified on Pixel 7 Pro: install + launch reaches android_main.

Compiler (entry-point linkage):
  Top-level fn defs default to LLVM internal linkage and are lazily
  lowered (only `main` was eagerly lowered before). Added
  isExportedEntryName() — a small allowlist for names the OS loader
  calls: `main`, `android_main`, `ANativeActivity_onCreate`,
  `JNI_OnLoad`. These get eagerly lowered AND keep external linkage,
  so they actually land in .dynsym.

LSP (imports):
  DocumentStore now takes the install-discovered stdlib_paths and
  forwards them into resolveImportPath, mirroring the compiler. Before
  this, every `#import "modules/..."` resolved through the stdlib path
  failed silently inside the LSP and identifiers from those modules
  showed as `undefined variable`. Repro on label.sx: 1 false positive
  before, 0 after.
This commit is contained in:
agra
2026-05-18 23:09:55 +03:00
parent f41a121a29
commit f66cda6d11
5 changed files with 384 additions and 15 deletions

View File

@@ -20,6 +20,16 @@ const Function = inst_mod.Function;
const Module = mod_mod.Module;
const Builder = mod_mod.Builder;
/// Names that must keep external LLVM linkage because the OS loader (not
/// sx code) is the caller. Without this they'd default to internal and
/// either DCE away or stay hidden from the dynamic symbol table.
fn isExportedEntryName(name: []const u8) bool {
return std.mem.eql(u8, name, "main") or
std.mem.eql(u8, name, "android_main") or
std.mem.eql(u8, name, "ANativeActivity_onCreate") or
std.mem.eql(u8, name, "JNI_OnLoad");
}
// ── Scope ───────────────────────────────────────────────────────────────
const Binding = struct {
@@ -569,7 +579,7 @@ pub const Lowering = struct {
switch (decl.data) {
.const_decl => |cd| {
if (cd.value.data == .fn_decl) {
if (std.mem.eql(u8, cd.name, "main")) {
if (isExportedEntryName(cd.name)) {
self.lazyLowerFunction(cd.name);
}
} else if (cd.value.data == .comptime_expr) {
@@ -577,7 +587,7 @@ pub const Lowering = struct {
}
},
.fn_decl => |fd| {
if (std.mem.eql(u8, fd.name, "main")) {
if (isExportedEntryName(fd.name)) {
self.lazyLowerFunction(fd.name);
}
},
@@ -728,7 +738,7 @@ pub const Lowering = struct {
return;
}
func.is_extern = false; // promote from extern stub to real function
func.linkage = if (std.mem.eql(u8, name, "main")) .external else .internal;
func.linkage = if (isExportedEntryName(name)) .external else .internal;
if (fd.call_conv == .c) func.call_conv = .c;
// Set inst_counter to param count (params occupy refs 0..N-1)
std.debug.assert(func.params.len == fd.params.len); // AST and IR param counts must match
@@ -836,8 +846,11 @@ pub const Lowering = struct {
);
_ = func_id;
// Set linkage for main
if (std.mem.eql(u8, name, "main")) {
// Set linkage. Default for fn defs is `internal` (LLVM DCE-friendly,
// matches C `static`). isExportedEntryName lists the names the OS
// loader calls — `main`, Android NativeActivity hooks — which must
// stay externally visible.
if (isExportedEntryName(name)) {
self.builder.currentFunc().linkage = .external;
}