diff --git a/packages/cli/test/target-cpu.test.ts b/packages/cli/test/target-cpu.test.ts index 5bc6717c3..dec7de15b 100644 --- a/packages/cli/test/target-cpu.test.ts +++ b/packages/cli/test/target-cpu.test.ts @@ -54,7 +54,7 @@ test.runIf(process.platform === "linux" && process.arch === "x64")( expect(baseline).not.toContain("sanitize_address"); expect(await build(["--target-cpu=baseline"])).toBe(baseline); const v3 = await build(["--target-cpu=x86-64-v3"]); - expect(v3).toMatch(/^attributes #0 = \{ "target-cpu"="x86-64-v3" \}$/m); + expect(v3).toMatch(/^attributes #0 = \{ uwtable "target-cpu"="x86-64-v3" \}$/m); if (runsV3) expect((await execFileAsync(outPath)).stdout).toBe(expected); // The bootstrap's cached route builds without a level. expect(await build([], true)).toBe(baseline); diff --git a/packages/compiler/src/backend/llvm/cpu-dispatch.ts b/packages/compiler/src/backend/llvm/cpu-dispatch.ts index f24fc8674..ced356cca 100644 --- a/packages/compiler/src/backend/llvm/cpu-dispatch.ts +++ b/packages/compiler/src/backend/llvm/cpu-dispatch.ts @@ -18,10 +18,10 @@ const COPY_GROUPS: ReadonlyMap = new Map([ ["2", "4"], ]); -export function cpuDispatchAttributes(asan: string): string[] { +export function cpuDispatchAttributes(common: string): string[] { return [ - `attributes #3 = {${asan} "target-cpu"="x86-64-v3" }`, - `attributes #4 = { noinline${asan} "target-cpu"="x86-64-v3" }`, + `attributes #3 = {${common} "target-cpu"="x86-64-v3" }`, + `attributes #4 = { noinline${common} "target-cpu"="x86-64-v3" }`, ]; } diff --git a/packages/compiler/src/backend/llvm/emit-module.ts b/packages/compiler/src/backend/llvm/emit-module.ts index 15efa88c7..bb4d2421d 100644 --- a/packages/compiler/src/backend/llvm/emit-module.ts +++ b/packages/compiler/src/backend/llvm/emit-module.ts @@ -199,19 +199,22 @@ function readRange(dir: string, range: number): { result: ChunkResult; texts: st /** The attribute groups of the emitted functions: #0 for every function, * #1 for coroutine bodies and #2 for out-of-line record clones. Each carries - * the build's `--target-cpu` level, and `sanitize_address` in sanitized - * builds. A group needs one attribute: without either, #0 holds a string - * attribute that LLVM ignores. Dispatching builds add the x86-64-v3 copies' - * #3 and #4 (cpu-dispatch.ts). */ + * the build's `--target-cpu` level, `sanitize_address` in sanitized builds, + * and `uwtable` on native targets, so every function has an unwind table + * entry, including those LLVM infers `nounwind` (profilers and debuggers + * unwind through them). A group needs one attribute: without any, #0 holds a + * string attribute that LLVM ignores. Dispatching builds add the x86-64-v3 + * copies' #3 and #4 (cpu-dispatch.ts). */ function functionAttributes(emitter: LlEmitter, hasNoInlineRecordClone: boolean): string[] { const cpu = targetCpuAttribute(emitter.targetCpu); const asan = emitter.sanitizeAttributes ? " sanitize_address" : ""; - const base = `${asan}${cpu}` || ` "scriptc-function"`; + const common = `${asan}${emitter.wasi ? "" : " uwtable"}`; + const base = `${common}${cpu}` || ` "scriptc-function"`; return [ `attributes #0 = {${base} }`, - ...(emitter.usesCoroutines ? [`attributes #1 = {${asan} presplitcoroutine${cpu} }`] : []), - ...(hasNoInlineRecordClone ? [`attributes #2 = { noinline${asan}${cpu} }`] : []), - ...(emitter.cpuDispatch ? cpuDispatchAttributes(asan) : []), + ...(emitter.usesCoroutines ? [`attributes #1 = {${common} presplitcoroutine${cpu} }`] : []), + ...(hasNoInlineRecordClone ? [`attributes #2 = { noinline${common}${cpu} }`] : []), + ...(emitter.cpuDispatch ? cpuDispatchAttributes(common) : []), ]; } diff --git a/packages/compiler/src/backend/llvm/emitter.test.ts b/packages/compiler/src/backend/llvm/emitter.test.ts index 880685bf2..8e3aae24d 100644 --- a/packages/compiler/src/backend/llvm/emitter.test.ts +++ b/packages/compiler/src/backend/llvm/emitter.test.ts @@ -206,7 +206,7 @@ test("a target CPU level reaches every function attribute group and nothing else const plain = emitLlvmModule(module); expect(plain).not.toContain("target-cpu"); const v3 = emitLlvmModule(module, { targetCpu: "x86-64-v3" }); - expect(v3).toMatch(/^attributes #0 = \{ sanitize_address "target-cpu"="x86-64-v3" \}$/m); + expect(v3).toMatch(/^attributes #0 = \{ sanitize_address uwtable "target-cpu"="x86-64-v3" \}$/m); expect(v3.replaceAll(' "target-cpu"="x86-64-v3"', "")).toBe(plain); expect(emitLlvmModule(module, { targetCpu: "baseline" })).toBe(plain); }); @@ -215,13 +215,21 @@ test("only sanitized builds mark the emitted functions sanitize_address", () => const module = moduleFor([call()]); const sanitized = emitLlvmModule(module); expect(emitLlvmModule(module, { sanitizeAttributes: true })).toBe(sanitized); - expect(sanitized).toMatch(/^attributes #0 = \{ sanitize_address \}$/m); + expect(sanitized).toMatch(/^attributes #0 = \{ sanitize_address uwtable \}$/m); const plain = emitLlvmModule(module, { sanitizeAttributes: false }); expect(plain).not.toContain("sanitize_address"); - // A group needs an attribute: an ignored string attribute stands in. - expect(plain).toMatch(/^attributes #0 = \{ "scriptc-function" \}$/m); + expect(plain).toMatch(/^attributes #0 = \{ uwtable \}$/m); const v3 = emitLlvmModule(module, { sanitizeAttributes: false, targetCpu: "x86-64-v3" }); - expect(v3).toMatch(/^attributes #0 = \{ "target-cpu"="x86-64-v3" \}$/m); + expect(v3).toMatch(/^attributes #0 = \{ uwtable "target-cpu"="x86-64-v3" \}$/m); +}); + +test("native functions get unwind tables and wasm32-wasi functions do not", () => { + const module = moduleFor([call()]); + expect(emitLlvmModule(module)).toMatch(/^attributes #0 = \{ sanitize_address uwtable \}$/m); + const wasi = emitLlvmModule(module, { wasi: true, pointerBits: 32, sanitizeAttributes: false }); + expect(wasi).not.toContain("uwtable"); + // A group needs an attribute: an ignored string attribute stands in. + expect(wasi).toMatch(/^attributes #0 = \{ "scriptc-function" \}$/m); }); test("inline RC inlines every fast path at speed and keeps box releases as calls in release", () => { diff --git a/packages/compiler/test/bytes-element-emission.test.ts b/packages/compiler/test/bytes-element-emission.test.ts index c0a78a898..1998ff0af 100644 --- a/packages/compiler/test/bytes-element-emission.test.ts +++ b/packages/compiler/test/bytes-element-emission.test.ts @@ -404,7 +404,7 @@ test("large record clones stay outlined while small clones remain inlineable", ( expect(small).toContain("define internal ptr @sc_rclone_small(ptr %src) #0"); expect(small).not.toContain("attributes #2"); expect(large).toContain("define internal ptr @sc_rclone_large(ptr %src) #2"); - expect(large).toContain("attributes #2 = { noinline sanitize_address }"); + expect(large).toContain("attributes #2 = { noinline sanitize_address uwtable }"); }); test("Uint8ClampedArray stores clamp and round without a runtime call", () => { diff --git a/packages/compiler/test/cpu-dispatch-emission.test.ts b/packages/compiler/test/cpu-dispatch-emission.test.ts index 170c28ede..43d80b335 100644 --- a/packages/compiler/test/cpu-dispatch-emission.test.ts +++ b/packages/compiler/test/cpu-dispatch-emission.test.ts @@ -119,10 +119,10 @@ test("a dispatching x86-64 build gives each program function a baseline and an x for (const [name] of coroutines) expect(defs.has(`${name}.v3`), name).toBe(false); // Functions used as values are referenced by their dispatching name. expect(ll).not.toMatch(/ptr @[\w.$-]+\.(v3|base)(?!\()/); - expect(ll).toMatch(/^attributes #3 = \{ sanitize_address "target-cpu"="x86-64-v3" \}$/m); + expect(ll).toMatch(/^attributes #3 = \{ sanitize_address uwtable "target-cpu"="x86-64-v3" \}$/m); // Unsanitized builds leave sanitize_address out of the copies' groups too. expect(emit({ ...x86, cpuDispatch: true, sanitizeAttributes: false })).toMatch( - /^attributes #3 = \{ "target-cpu"="x86-64-v3" \}$/m, + /^attributes #3 = \{ uwtable "target-cpu"="x86-64-v3" \}$/m, ); // main sets the level before anything else runs. expect(defs.get("main")).toMatch( diff --git a/packages/compiler/test/generator-emission.test.ts b/packages/compiler/test/generator-emission.test.ts index 6601813bd..2442c5e09 100644 --- a/packages/compiler/test/generator-emission.test.ts +++ b/packages/compiler/test/generator-emission.test.ts @@ -56,7 +56,7 @@ console.log(sum); expect(definition(llvm, "sc_f_range")).not.toContain("@scr_coro_yield_f64("); expect(llvm).not.toContain("@scr_gen_take_out_f64("); expect(llvm).toContain("%ScrGenHead = type { i64, ptr, ptr, i32, %ScrGenSlot }"); - expect(llvm).toContain("attributes #1 = { presplitcoroutine }"); + expect(llvm).toContain("attributes #1 = { uwtable presplitcoroutine }"); expect(definition(llvm, "sc_gs_range")).toContain("call ptr @scr_gen_new_stackless("); expect(llvm).not.toContain("@scr_gen_yield_f64("); });