feat(lang): float and double types with ordinary operators
`float` (32-bit) and `double` (64-bit) with + - * / %, comparisons and unary minus. Decimal literals take their type from context and stay `fixed` elsewhere; int and long promote implicitly (LUDIC_WARN_FLOAT_PROMOTE=1 lists every promotion). float(), double(), int(), long() and fixed() convert; floats(n)/doubles(n) buffers; float fields, globals, constants and parameters; Math.* computes in float for float arguments; string/print write the shortest round-tripping decimal; float_bits/float_from_bits expose the bits. @deterministic code may not use floats. The f_* runtime helpers stay as they are. Editors know the new type words; the JetBrains plugin is 1.5.0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
f92d7f89c6
commit
e010c2cecc
40 changed files with 67726 additions and 57272 deletions
|
|
@ -33,6 +33,13 @@ function emit_index_addr(e: Node) -> pointer {
|
|||
g_addr_ty = "fixed"
|
||||
return rf
|
||||
}
|
||||
if (base.ty == "floats") or (base.ty == "doubles") { # IEEE float / double elements
|
||||
var et = "float"
|
||||
if (base.ty == "doubles") { et = "double" }
|
||||
let rd = emit_bind(`getelementptr inbounds {et}, ptr {base.code}, i32 {bi.code}`)
|
||||
g_addr_ty = et
|
||||
return rd
|
||||
}
|
||||
if (base.ty == "pointers") { # a `pointers` buffer: pointer elements
|
||||
let rp = emit_bind(`getelementptr inbounds ptr, ptr {base.code}, i32 {bi.code}`)
|
||||
g_addr_ty = "ptr"
|
||||
|
|
|
|||
|
|
@ -1067,12 +1067,14 @@ function emit_call(e: Node) -> Val {
|
|||
if (name == "string") { # string(x): int/bool/fixed/long -> text, a string passes through
|
||||
let a = emit_expr(e.kids[0])
|
||||
if (llty(a.ty) == "ptr") { return a }
|
||||
if is_fp(a.ty) { return emit_fp_str(a) }
|
||||
if (llty(a.ty) == "i64") { g_uses_longstr = true; return val(emit_bind(`call ptr @lp_long_str(i64 {a.code})`), "string") }
|
||||
g_uses_intstr = true
|
||||
return val(emit_bind(`call ptr @lp_int_str(i32 {a.code})`), "string")
|
||||
}
|
||||
if (name == "print") { # print(x): a value + newline (string, long, or int)
|
||||
let a = emit_expr(e.kids[0])
|
||||
var a = emit_expr(e.kids[0])
|
||||
if is_fp(a.ty) { a = emit_fp_str(a) }
|
||||
if (llty(a.ty) == "ptr") { emit(" call i32 (ptr, ...) @printf(ptr @.fmt_line, ptr " + `{a.code})\n`) }
|
||||
else { if (llty(a.ty) == "i64") { emit(" call i32 (ptr, ...) @printf(ptr @.fmt_long, i64 " + `{a.code})\n`) }
|
||||
else { emit(" call i32 (ptr, ...) @printf(ptr @.fmt_int, i32 " + `{a.code})\n`) } }
|
||||
|
|
@ -1089,7 +1091,37 @@ function emit_call(e: Node) -> Val {
|
|||
let w = emit_bind(`zext i32 {by} to i64`)
|
||||
return val(emit_bind(`call ptr @malloc(i64 {w})`), "words")
|
||||
}
|
||||
if (name == "fixed") { let a = emit_expr(e.kids[0]); return val(emit_bind(`shl i32 {a.code}, 16`), "fixed") }
|
||||
if (name == "fixed") {
|
||||
let a = emit_expr(e.kids[0])
|
||||
if is_fp(a.ty) { return emit_fp_to_fixed(a) }
|
||||
if (a.lit != null) { return val(a.code, "fixed") } # fixed(1.5) is the literal itself
|
||||
return val(emit_bind(`shl i32 {a.code}, 16`), "fixed")
|
||||
}
|
||||
# float(x) / double(x) / int(x) / long(x): explicit numeric conversions
|
||||
if ((name == "float") or (name == "double")) and (find_fn(name) == null) and len(e.kids) == 1 {
|
||||
return emit_fp_convert(name, emit_expr(e.kids[0]))
|
||||
}
|
||||
if (name == "int") and (find_fn(name) == null) and len(e.kids) == 1 {
|
||||
let a = emit_expr(e.kids[0])
|
||||
if is_fp(a.ty) { return emit_fp_to_int(a) }
|
||||
if (a.ty == "fixed") { return val(emit_bind(`ashr i32 {a.code}, 16`), "int") }
|
||||
if (llty(a.ty) == "i64") { return val(emit_bind(`trunc i64 {a.code} to i32`), "int") }
|
||||
return val(a.code, "int")
|
||||
}
|
||||
if (name == "long") and (find_fn(name) == null) and len(e.kids) == 1 {
|
||||
let a = emit_expr(e.kids[0])
|
||||
if is_fp(a.ty) { return emit_fp_to_long(a) }
|
||||
return val(to_long(a), "long")
|
||||
}
|
||||
if (name == "float_bits") and (find_fn(name) == null) { return emit_float_bits(emit_expr(e.kids[0])) }
|
||||
if (name == "float_from_bits") and (find_fn(name) == null) { return emit_float_from_bits(emit_expr(e.kids[0])) }
|
||||
if (name == "double_bits") and (find_fn(name) == null) { return emit_double_bits(emit_expr(e.kids[0])) }
|
||||
if (name == "double_from_bits") and (find_fn(name) == null) { return emit_double_from_bits(emit_expr(e.kids[0])) }
|
||||
if ((name == "floats") or (name == "doubles")) and (find_fn(name) == null) {
|
||||
var ft = "float"
|
||||
if (name == "doubles") { ft = "double" }
|
||||
return emit_fp_buffer(ft, emit_expr(e.kids[0]))
|
||||
}
|
||||
if (name == "floor") { let a = emit_expr(e.kids[0]); return val(emit_bind(`ashr i32 {a.code}, 16`), "int") }
|
||||
# --- the testing framework's assertions (see emit_test_runner) --------------
|
||||
# expect(cond) / expect_eq(a, b) / expect_near(a, b, tol): on failure they set
|
||||
|
|
@ -1349,7 +1381,8 @@ function emit_call(e: Node) -> Val {
|
|||
function emit_expr(e: Node) -> Val {
|
||||
if (e == null) { return val("0", "int") }
|
||||
if e.kind == E_INT { return val(itoa(e.ival), "int") }
|
||||
if e.kind == E_FLOAT { return val(itoa(e.ival), "fixed") }
|
||||
if e.kind == E_FLOAT { let fv = val(itoa(e.ival), "fixed"); fv.lit = e; return fv }
|
||||
if e.kind == E_PREVAL { return g_prevals[e.ival] }
|
||||
if e.kind == E_BOOL { return val(itoa(e.ival), "bool") }
|
||||
if e.kind == E_NULL { return val("null", "pointer") }
|
||||
if e.kind == S_SPAWN { return val(emit_spawn(e), "entity") } # `let e = spawn Model { … }`
|
||||
|
|
@ -1385,7 +1418,11 @@ function emit_expr(e: Node) -> Val {
|
|||
# expression's real type — so `const X: fixed = 10.0` yields a `fixed`, not
|
||||
# the raw Q16.16 bits mislabelled `int`. Every existing const is an int
|
||||
# literal, for which this is byte-identical to the old immediate.
|
||||
if g.kind == N_CONST { return emit_expr(g.a) }
|
||||
if g.kind == N_CONST {
|
||||
let cv = emit_expr(g.a)
|
||||
if is_fp(g.ty) { return val(to_fp(cv, g.ty, `const {e.s}`), g.ty) }
|
||||
return cv
|
||||
}
|
||||
let r = emit_bind(`load {llty(g.ty)}, ptr @g_{e.s}`)
|
||||
return val(r, g.ty)
|
||||
}
|
||||
|
|
@ -1438,6 +1475,15 @@ function emit_expr(e: Node) -> Val {
|
|||
if e.kind == E_BIN { return emit_bin(e) }
|
||||
if e.kind == E_UN {
|
||||
let a = emit_expr(e.a)
|
||||
if is_fp(a.ty) {
|
||||
if (e.s == ("-")) { return val(emit_bind(`fneg {a.ty} {a.code}`), a.ty) }
|
||||
perr(`operator {e.s} does not apply to {a.ty}`)
|
||||
}
|
||||
if (e.s == ("-")) and (a.lit != null) { # -1.5 stays a literal: exact in a float context
|
||||
let nv = val(emit_bind(`sub i32 0, {a.code}`), "fixed")
|
||||
nv.lit = e
|
||||
return nv
|
||||
}
|
||||
if (llty(a.ty) == "i64") { # negate / bit-flip a long, staying 64-bit
|
||||
if (e.s == ("-")) { return val(emit_bind(`sub i64 0, {a.code}`), "long") }
|
||||
if (e.s == "~") { return val(emit_bind(`xor i64 {a.code}, -1`), "long") }
|
||||
|
|
|
|||
|
|
@ -2,8 +2,11 @@
|
|||
# module header. Mirrors the pieces of compiler/back/ that this subset needs.
|
||||
# structs and slices are references, so every non-scalar type lowers to `ptr`.
|
||||
|
||||
property Val { code: pointer = null, ty: pointer = null }
|
||||
function val(code: pointer, ty: pointer) -> Val { let v = new Val; v.code = code; v.ty = ty; return v }
|
||||
# lit: the expression of a value made only of numeric literals with at least one
|
||||
# decimal among them (`1.5`, `-0.25`, `1.0 / 3.0`). It is `fixed` on its own and
|
||||
# is re-evaluated exactly in a float context — see emit_float.ludic.
|
||||
property Val { code: pointer = null, ty: pointer = null, lit: Node = null }
|
||||
function val(code: pointer, ty: pointer) -> Val { let v = new Val; v.code = code; v.ty = ty; v.lit = null; return v }
|
||||
|
||||
var head: Buf # module-level: types, globals, string constants
|
||||
var code: Buf # function bodies
|
||||
|
|
@ -125,11 +128,13 @@ function llty(t: pointer) -> pointer {
|
|||
if is_bare_enum(t) { return "i32" } # a payload-free enum is an int by another name
|
||||
if (t == "countdown") { return "i32" } # an int the engine counts down to 0 each Update
|
||||
if (t == "long") { return "i64" } # a 64-bit signed integer
|
||||
if (t == "float") { return "float" } # IEEE single precision
|
||||
if (t == "double") { return "double" } # IEEE double precision
|
||||
if (t == "Vector") { return "i64" } # a 2D vector: (x, y) fixeds packed into one i64
|
||||
if (t == "IVec2") { return "i64" } # an integer 2D vector: (x, y) ints packed into one i64
|
||||
if (t == "Rect") { return "i128" } # a rectangle: (x, y, w, h) fixeds packed into one i128
|
||||
if (t == "byte") { return "i8" } # a single byte (p[i] on a raw ptr)
|
||||
if (t == "words") or (t == "fixeds") or (t == "pointers") { return "ptr" } # typed buffers
|
||||
if (t == "words") or (t == "fixeds") or (t == "pointers") or (t == "floats") or (t == "doubles") { return "ptr" } # typed buffers
|
||||
if (t == "void") { return "void" }
|
||||
return "ptr"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ function emit_params_sig(d: Node) -> void {
|
|||
|
||||
function emit_fn(d: Node) -> void {
|
||||
ll_t = 0; ll_lbl = 0; g_term = false; loc_reset(); nloop = 0
|
||||
det_enter(d.s)
|
||||
g_cur_scene = null # a function belongs to no scene: `become` leaves the live one
|
||||
ret_ty = d.ty
|
||||
let fbody = buf_new()
|
||||
|
|
@ -40,6 +41,7 @@ function emit_fn(d: Node) -> void {
|
|||
emit(buf_str(falloc))
|
||||
emit(buf_str(fbody))
|
||||
emit("}\n\n")
|
||||
g_det_ctx = ""
|
||||
}
|
||||
|
||||
function emit_main(d: Node) -> void {
|
||||
|
|
@ -208,6 +210,9 @@ function emit_program() -> void {
|
|||
code = buf_new()
|
||||
g_uses_str = false
|
||||
g_uses_intstr = false
|
||||
g_uses_fpstr = false
|
||||
g_fp_decls = new []pointer
|
||||
g_prevals = new []Val
|
||||
g_uses_strslice = false
|
||||
g_uses_loopback = false
|
||||
g_uses_expect = false
|
||||
|
|
@ -256,7 +261,8 @@ function emit_program() -> void {
|
|||
if g_uses_world_despawn { emit_world_despawn_fn() } # #84: @fn_world_despawn, after all World.despawn / esys_bounds-kill uses are seen
|
||||
if g_uses_loopback { emit_loopback() } # built-in transport, after all net_send/net_poll uses are seen
|
||||
if g_uses_str { emit_str_prelude() } # @lp_str_eq / @lp_str_concat, after all uses are seen
|
||||
if g_uses_intstr { emit_int_str() } # @lp_int_str, for string(int) in interpolation
|
||||
if g_uses_intstr { emit_int_str() }
|
||||
if g_uses_fpstr { emit_fp_str_fn() } # @lp_int_str, for string(int) in interpolation
|
||||
if g_uses_longstr { emit_long_str() } # @lp_long_str, for string(long) / long interpolation
|
||||
if g_uses_strslice { emit_str_slice() } # @lp_str_slice, for s[a..b]
|
||||
if g_uses_mathrt { emit_math_prelude() } # @lp_fx_sqrt / @lp_fx_sin + the sine table
|
||||
|
|
|
|||
|
|
@ -108,6 +108,10 @@ function to_fixed(v: Val) -> pointer {
|
|||
# pair the language has: int (i32) <-> long (i64). int widens with sext, long
|
||||
# narrows with trunc; everything else (same width, or ptr) passes through.
|
||||
function coerce_code(v: Val, target: pointer) -> pointer {
|
||||
if is_fp(target) { return to_fp(v, target, `a {target} slot`) }
|
||||
if is_fp(v.ty) and not is_fp(target) and (llty(target) != "ptr") {
|
||||
perr(`a {v.ty} does not convert to {target} implicitly — write int(x) or fixed(x)`)
|
||||
}
|
||||
let lt = llty(target)
|
||||
let vt = llty(v.ty)
|
||||
if (lt == vt) { return v.code }
|
||||
|
|
@ -140,7 +144,7 @@ function emit_str_op(op: pointer, a: Val, b: Val) -> Val {
|
|||
# the result type of a unary arithmetic operator on a value of type `ty`: fixed
|
||||
# and long survive, every other 32-bit scalar (int/bool/enum) collapses to int
|
||||
function arith_ty(ty: pointer) -> pointer {
|
||||
if (ty == "fixed") or (ty == "long") { return ty }
|
||||
if (ty == "fixed") or (ty == "long") or is_fp(ty) { return ty }
|
||||
return "int"
|
||||
}
|
||||
|
||||
|
|
@ -149,7 +153,12 @@ function emit_bin(e: Node) -> Val {
|
|||
let a = emit_expr(e.a)
|
||||
let b = emit_expr(e.b)
|
||||
let isnull = e.a.kind == E_NULL or e.b.kind == E_NULL
|
||||
return emit_bin_vals(e.s, a, b, isnull)
|
||||
let r = emit_bin_vals(e.s, a, b, isnull)
|
||||
# literal arithmetic stays a literal (for a float context) when a decimal is in it
|
||||
if not is_cmp(e.s) and not is_fp(r.ty) and (a.lit != null or b.lit != null) {
|
||||
if (a.lit != null or e.a.kind == E_INT) and (b.lit != null or e.b.kind == E_INT) { r.lit = e }
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
# lower `a <op> b` on two already-evaluated operands. Shared by binary expressions
|
||||
|
|
@ -170,6 +179,7 @@ function emit_bin_vals(op: pointer, a: Val, b: Val, isnull: bool) -> Val {
|
|||
# a 64-bit operand (and no fixed/ptr involved) promotes the whole expression to
|
||||
# i64: the other side widens with sext, and the result stays `long`.
|
||||
let lng = ((llty(a.ty) == "i64") or (llty(b.ty) == "i64")) and not fx and not ptrish
|
||||
if is_fp(a.ty) or is_fp(b.ty) { return emit_fp_bin(op, a, b) }
|
||||
if is_cmp(op) {
|
||||
var ac = a.code; var bc = b.code
|
||||
var ct = "i32"
|
||||
|
|
|
|||
457
selfhost/backend/emit_float.ludic
Normal file
457
selfhost/backend/emit_float.ludic
Normal file
|
|
@ -0,0 +1,457 @@
|
|||
# emit_float.ludic — IEEE floating point: `float` (32-bit) and `double` (64-bit).
|
||||
#
|
||||
# Rules (LANGUAGE.md, "Floating point"):
|
||||
# - an int (or long) operand promotes to the float type of the other side;
|
||||
# float with double promotes to double
|
||||
# - a decimal literal (`1.5`) takes a float type from its context — an
|
||||
# operand, a typed binding, a parameter — and is `fixed` otherwise
|
||||
# - fixed never mixes with float silently: float(x) / fixed(x) convert
|
||||
# - float -> int, double -> float are explicit: int(x), float(x)
|
||||
#
|
||||
# The compiler has no floating point of its own, so a literal travels as its
|
||||
# decimal text and LLVM parses it (a `double` constant, fptrunc'd to `float`).
|
||||
|
||||
function is_fp(t: pointer) -> bool { return (t == "float") or (t == "double") }
|
||||
|
||||
# @deterministic functions and handlers compute the same bits on every machine,
|
||||
# which IEEE float (fused multiply-adds, libm differences) does not promise
|
||||
var g_det_names: []pointer = new []pointer
|
||||
var g_det_ctx: pointer = "" # the @deterministic declaration being emitted, or ""
|
||||
|
||||
function det_enter(name: pointer) -> void {
|
||||
g_det_ctx = ""
|
||||
var i = 0
|
||||
while i < len(g_det_names) { if (g_det_names[i] == name) { g_det_ctx = name }; i += 1 }
|
||||
}
|
||||
|
||||
function fp_guard(t: pointer) -> void {
|
||||
if not (g_det_ctx == "") {
|
||||
perr(`@deterministic {g_det_ctx} cannot compute with {t}: floating point differs between machines — use fixed`)
|
||||
}
|
||||
}
|
||||
|
||||
# the wider of two operand types, when at least one is float/double
|
||||
function fp_result(a: pointer, b: pointer) -> pointer {
|
||||
if (a == "double") or (b == "double") { return "double" }
|
||||
return "float"
|
||||
}
|
||||
|
||||
# a literal-only expression (Val.lit) evaluated exactly in float type t
|
||||
function fp_const(e: Node, t: pointer) -> pointer {
|
||||
if e.kind == E_FLOAT { return fp_lit_code(e.s, t) }
|
||||
if e.kind == E_INT { return fp_lit_code(itoa(e.ival), t) }
|
||||
if e.kind == E_UN {
|
||||
let x = fp_const(e.a, t)
|
||||
return emit_bind(`fneg {t} {x}`)
|
||||
}
|
||||
if e.kind == E_BIN {
|
||||
let a = fp_const(e.a, t)
|
||||
let b = fp_const(e.b, t)
|
||||
var inst = "fadd"
|
||||
if (e.s == ("-")) { inst = "fsub" }
|
||||
if (e.s == ("*")) { inst = "fmul" }
|
||||
if (e.s == ("/")) { inst = "fdiv" }
|
||||
if (e.s == ("%")) { inst = "frem" }
|
||||
return emit_bind(`{inst} {t} {a}, {b}`)
|
||||
}
|
||||
perr("internal: not a literal expression")
|
||||
return "0.0"
|
||||
}
|
||||
|
||||
# the zero a slot of LLVM type lt starts at
|
||||
function zero_of(lt: pointer) -> pointer {
|
||||
if (lt == "ptr") { return "null" }
|
||||
if (lt == "float") or (lt == "double") { return "0.0" }
|
||||
return "0"
|
||||
}
|
||||
|
||||
# a literal's decimal text as a value of type t
|
||||
function fp_lit_code(text: pointer, t: pointer) -> pointer {
|
||||
var d = text
|
||||
if not str_has(d, '.') { d = d + ".0" }
|
||||
if (t == "double") { return d }
|
||||
return emit_bind(`fptrunc double {d} to float`)
|
||||
}
|
||||
|
||||
function str_has(s: pointer, ch: int) -> bool {
|
||||
var i = 0
|
||||
while i < len(s) { if s[i] == ch { return true }; i += 1 }
|
||||
return false
|
||||
}
|
||||
|
||||
# is this register text an integer constant (`42`, `-7`)?
|
||||
function is_int_const(code: pointer) -> bool {
|
||||
let n = len(code)
|
||||
if n == 0 { return false }
|
||||
var i = 0
|
||||
if code[0] == '-' { i = 1 }
|
||||
if i >= n { return false }
|
||||
while i < n { if code[i] < '0' or code[i] > '9' { return false }; i += 1 }
|
||||
return true
|
||||
}
|
||||
|
||||
# v as a value of float type t (implicit conversions only). `what` names the
|
||||
# context for the error message.
|
||||
function to_fp(v: Val, t: pointer, what: pointer) -> pointer {
|
||||
fp_guard(t)
|
||||
if (v.ty == t) { return v.code }
|
||||
if (v.ty == "float") and (t == "double") { return emit_bind(`fpext float {v.code} to double`) }
|
||||
if (v.ty == "double") and (t == "float") {
|
||||
perr(`{what}: a double does not narrow to float implicitly — write float(x)`)
|
||||
}
|
||||
if (v.lit != null) { return fp_const(v.lit, t) }
|
||||
if (v.ty == "fixed") {
|
||||
perr(`{what}: fixed and {t} do not mix implicitly — convert with {t}(x) or fixed(x)`)
|
||||
}
|
||||
let lt = llty(v.ty)
|
||||
if (lt == "i64") { fp_promote_note(v.ty, t); return emit_bind(`sitofp i64 {v.code} to {t}`) }
|
||||
if (lt == "i32") {
|
||||
if is_int_const(v.code) { return fp_lit_code(v.code, t) }
|
||||
fp_promote_note(v.ty, t)
|
||||
return emit_bind(`sitofp i32 {v.code} to {t}`)
|
||||
}
|
||||
perr(`{what}: a {v.ty} is not a number`)
|
||||
return v.code
|
||||
}
|
||||
|
||||
# LUDIC_WARN_FLOAT_PROMOTE=1: report every implicit promotion of a computed
|
||||
# (non-constant) integer to float — the audit a migration from float bit
|
||||
# patterns in ints relies on, since such a value would silently change meaning
|
||||
var g_warn_promote: int = -1
|
||||
function fp_promote_note(from: pointer, t: pointer) -> void {
|
||||
if g_warn_promote < 0 {
|
||||
g_warn_promote = 0
|
||||
if (getenv("LUDIC_WARN_FLOAT_PROMOTE") != null) { g_warn_promote = 1 }
|
||||
}
|
||||
if g_warn_promote == 0 { return }
|
||||
var file = g_err_file
|
||||
if (file == null) { file = "" }
|
||||
let m = `{file}:{itoa(g_err_line)}: warning: a computed {from} is promoted to {t}\n`
|
||||
file_write(file_stderr(), m, len(m))
|
||||
}
|
||||
|
||||
function fcmp_code(op: pointer) -> pointer {
|
||||
if (op == ("<")) { return "olt" }
|
||||
if (op == ("<=")) { return "ole" }
|
||||
if (op == (">")) { return "ogt" }
|
||||
if (op == (">=")) { return "oge" }
|
||||
if (op == ("==")) { return "oeq" }
|
||||
return "une" # != is true for NaN, as everywhere
|
||||
}
|
||||
|
||||
# `a <op> b` where at least one side is float/double
|
||||
function emit_fp_bin(op: pointer, a: Val, b: Val) -> Val {
|
||||
var t = "float"
|
||||
if is_fp(a.ty) and is_fp(b.ty) { t = fp_result(a.ty, b.ty) }
|
||||
else { if is_fp(a.ty) { t = a.ty } else { t = b.ty } }
|
||||
let ac = to_fp(a, t, `the left side of {op}`)
|
||||
let bc = to_fp(b, t, `the right side of {op}`)
|
||||
if is_cmp(op) {
|
||||
let c = emit_bind(`fcmp {fcmp_code(op)} {t} {ac}, {bc}`)
|
||||
return val(emit_bind(`zext i1 {c} to i32`), "bool")
|
||||
}
|
||||
var inst = ""
|
||||
if (op == ("+")) { inst = "fadd" }
|
||||
if (op == ("-")) { inst = "fsub" }
|
||||
if (op == ("*")) { inst = "fmul" }
|
||||
if (op == ("/")) { inst = "fdiv" }
|
||||
if (op == ("%")) { inst = "frem" }
|
||||
if (inst == "") { perr(`operator {op} does not apply to {t}`) }
|
||||
return val(emit_bind(`{inst} {t} {ac}, {bc}`), t)
|
||||
}
|
||||
|
||||
# float(x) / double(x): any number to that float type, explicitly
|
||||
function emit_fp_convert(t: pointer, v: Val) -> Val {
|
||||
fp_guard(t)
|
||||
if (v.lit != null) { return val(fp_const(v.lit, t), t) }
|
||||
if is_fp(v.ty) {
|
||||
if (v.ty == t) { return v }
|
||||
if (t == "double") { return val(emit_bind(`fpext float {v.code} to double`), t) }
|
||||
return val(emit_bind(`fptrunc double {v.code} to float`), t)
|
||||
}
|
||||
if (v.ty == "fixed") {
|
||||
let f = emit_bind(`sitofp i32 {v.code} to {t}`)
|
||||
return val(emit_bind(`fdiv {t} {f}, 65536.0`), t)
|
||||
}
|
||||
return val(to_fp(v, t, `{t}(x)`), t)
|
||||
}
|
||||
|
||||
# int(x) on a float: truncates toward zero, as C does
|
||||
function emit_fp_to_int(v: Val) -> Val {
|
||||
return val(emit_bind(`fptosi {v.ty} {v.code} to i32`), "int")
|
||||
}
|
||||
|
||||
# long(x) on a float
|
||||
function emit_fp_to_long(v: Val) -> Val {
|
||||
return val(emit_bind(`fptosi {v.ty} {v.code} to i64`), "long")
|
||||
}
|
||||
|
||||
# fixed(x) on a float: truncated toward zero to Q16.16, as int(x) truncates
|
||||
function emit_fp_to_fixed(v: Val) -> Val {
|
||||
let m = emit_bind(`fmul {v.ty} {v.code}, 65536.0`)
|
||||
return val(emit_bind(`fptosi {v.ty} {m} to i32`), "fixed")
|
||||
}
|
||||
|
||||
# float_bits(x) / float_from_bits(i): the IEEE bit pattern of a float, both
|
||||
# ways — what a GPU buffer or a file holds
|
||||
function emit_float_bits(v: Val) -> Val {
|
||||
let f = to_fp(v, "float", "float_bits(x)")
|
||||
return val(emit_bind(`bitcast float {f} to i32`), "int")
|
||||
}
|
||||
function emit_float_from_bits(v: Val) -> Val {
|
||||
return val(emit_bind(`bitcast i32 {v.code} to float`), "float")
|
||||
}
|
||||
function emit_double_bits(v: Val) -> Val {
|
||||
let f = to_fp(v, "double", "double_bits(x)")
|
||||
return val(emit_bind(`bitcast double {f} to i64`), "long")
|
||||
}
|
||||
function emit_double_from_bits(v: Val) -> Val {
|
||||
return val(emit_bind(`bitcast i64 {to_long(v)} to double`), "double")
|
||||
}
|
||||
|
||||
# ---- text -------------------------------------------------------------------
|
||||
var g_uses_fpstr: bool = false
|
||||
|
||||
# string(x) for a float/double: the shortest text that reads back as x
|
||||
function emit_fp_str(v: Val) -> Val {
|
||||
g_uses_fpstr = true
|
||||
var d = v.code
|
||||
var single = "0"
|
||||
if (v.ty == "float") { d = emit_bind(`fpext float {v.code} to double`); single = "1" }
|
||||
return val(emit_bind(`call ptr @lp_fp_str(double {d}, i32 {single})`), "string")
|
||||
}
|
||||
|
||||
# @lp_fp_str(v, single): "%.*g" with the fewest digits that round-trip (through
|
||||
# float when single), plus ".0" when the text would read as an int
|
||||
function emit_fp_str_fn() -> void {
|
||||
fp_declare("declare i32 @snprintf(ptr, i64, ptr, ...)\n")
|
||||
fp_declare("declare double @strtod(ptr, ptr)\n")
|
||||
fp_declare("declare ptr @strpbrk(ptr, ptr)\n")
|
||||
emith("@.fmt_fpg = private unnamed_addr constant [5 x i8] c\"%.*g\\00\"\n")
|
||||
emith("@.fp_marks = private unnamed_addr constant [8 x i8] c\".eEnNiI\\00\"\n")
|
||||
emith("define ptr @lp_fp_str(double %v, i32 %single) {\n")
|
||||
emith("entry:\n %buf = call ptr @malloc(i64 40)\n br label %try\n")
|
||||
emith("try:\n %p = phi i32 [ 6, %entry ], [ %p1, %again ]\n")
|
||||
emith(" %w = call i32 (ptr, i64, ptr, ...) @snprintf(ptr %buf, i64 36, ptr @.fmt_fpg, i32 %p, double %v)\n")
|
||||
emith(" %r = call double @strtod(ptr %buf, ptr null)\n")
|
||||
emith(" %rf = fptrunc double %r to float\n %vf = fptrunc double %v to float\n")
|
||||
emith(" %eqf = fcmp oeq float %rf, %vf\n %eqd = fcmp oeq double %r, %v\n")
|
||||
emith(" %is1 = icmp ne i32 %single, 0\n %eq = select i1 %is1, i1 %eqf, i1 %eqd\n")
|
||||
emith(" %p1 = add i32 %p, 1\n %last = icmp sge i32 %p, 17\n %stop = or i1 %eq, %last\n")
|
||||
emith(" br i1 %stop, label %done, label %again\n")
|
||||
emith("again:\n br label %try\n")
|
||||
emith("done:\n %mark = call ptr @strpbrk(ptr %buf, ptr @.fp_marks)\n %whole = icmp eq ptr %mark, null\n")
|
||||
emith(" br i1 %whole, label %dot, label %out\n")
|
||||
emith("dot:\n %n64 = call i64 @strlen(ptr %buf)\n %e0 = getelementptr inbounds i8, ptr %buf, i64 %n64\n")
|
||||
emith(" store i8 46, ptr %e0\n %n1 = add i64 %n64, 1\n %e1 = getelementptr inbounds i8, ptr %buf, i64 %n1\n")
|
||||
emith(" store i8 48, ptr %e1\n %n2 = add i64 %n64, 2\n %e2 = getelementptr inbounds i8, ptr %buf, i64 %n2\n")
|
||||
emith(" store i8 0, ptr %e2\n br label %out\n")
|
||||
emith("out:\n ret ptr %buf\n}\n")
|
||||
}
|
||||
|
||||
# ---- buffers ----------------------------------------------------------------
|
||||
# floats(n) / doubles(n): n uninitialised elements, indexed like words
|
||||
function emit_fp_buffer(t: pointer, n: Val) -> Val {
|
||||
var sz = "4"; var ty = "floats"
|
||||
if (t == "double") { sz = "8"; ty = "doubles" }
|
||||
let by = emit_bind(`mul i32 {n.code}, {sz}`)
|
||||
let w = emit_bind(`zext i32 {by} to i64`)
|
||||
return val(emit_bind(`call ptr @malloc(i64 {w})`), ty)
|
||||
}
|
||||
|
||||
# ---- Math.* on floats ---------------------------------------------------------
|
||||
var g_fp_decls: []pointer
|
||||
|
||||
# declare a libm function once per program
|
||||
function fp_declare(line: pointer) -> void {
|
||||
var i = 0
|
||||
while i < len(g_fp_decls) { if (g_fp_decls[i] == line) { return }; i += 1 }
|
||||
push(g_fp_decls, line)
|
||||
emith(line)
|
||||
}
|
||||
|
||||
# call a one-argument libm/intrinsic function of type t
|
||||
function fp_call1(base: pointer, t: pointer, x: pointer) -> pointer {
|
||||
var fname = base
|
||||
if (t == "float") { fname = base + "f" }
|
||||
fp_declare(`declare {t} @{fname}({t})\n`)
|
||||
return emit_bind(`call {t} @{fname}({t} {x})`)
|
||||
}
|
||||
function fp_call2(base: pointer, t: pointer, x: pointer, y: pointer) -> pointer {
|
||||
var fname = base
|
||||
if (t == "float") { fname = base + "f" }
|
||||
fp_declare(`declare {t} @{fname}({t}, {t})\n`)
|
||||
return emit_bind(`call {t} @{fname}({t} {x}, {t} {y})`)
|
||||
}
|
||||
function fp_intrinsic1(name: pointer, t: pointer, x: pointer) -> pointer {
|
||||
var sfx = "f64"
|
||||
if (t == "float") { sfx = "f32" }
|
||||
fp_declare(`declare {t} @llvm.{name}.{sfx}({t})\n`)
|
||||
return emit_bind(`call {t} @llvm.{name}.{sfx}({t} {x})`)
|
||||
}
|
||||
function fp_intrinsic2(name: pointer, t: pointer, x: pointer, y: pointer) -> pointer {
|
||||
var sfx = "f64"
|
||||
if (t == "float") { sfx = "f32" }
|
||||
fp_declare(`declare {t} @llvm.{name}.{sfx}({t}, {t})\n`)
|
||||
return emit_bind(`call {t} @llvm.{name}.{sfx}({t} {x}, {t} {y})`)
|
||||
}
|
||||
|
||||
# min / max as a comparison and a select: `x < y ? x : y`
|
||||
function fp_pick(meth: pointer, t: pointer, x: pointer, y: pointer) -> pointer {
|
||||
var cc = "olt"
|
||||
if (meth == "max") { cc = "ogt" }
|
||||
let c = emit_bind(`fcmp {cc} {t} {x}, {y}`)
|
||||
return emit_bind(`select i1 {c}, {t} {x}, {t} {y}`)
|
||||
}
|
||||
|
||||
# the float type a Math call works in, given its already-evaluated first
|
||||
# argument and the static types of the rest; "" when it is not a float call
|
||||
function fp_math_type(first: Val, e: Node) -> pointer {
|
||||
var t = ""
|
||||
if is_fp(first.ty) { t = first.ty }
|
||||
var i = 1
|
||||
while i < len(e.kids) {
|
||||
let st = static_type(e.kids[i])
|
||||
if (st != null) and is_fp(st) {
|
||||
if (t == "") { t = st } else { t = fp_result(t, st) }
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
function fp_arg(e: Node, i: int, t: pointer, meth: pointer) -> pointer {
|
||||
let v = emit_expr(e.kids[i])
|
||||
return to_fp(v, t, `Math.{meth}`)
|
||||
}
|
||||
|
||||
# Math.<meth>(…) in float type t; the first argument is already evaluated
|
||||
function emit_fp_math(meth: pointer, t: pointer, first: Val, e: Node) -> Val {
|
||||
let x = to_fp(first, t, `Math.{meth}`)
|
||||
if (meth == "abs") { return val(fp_intrinsic1("fabs", t, x), t) }
|
||||
if (meth == "sqrt") { return val(fp_intrinsic1("sqrt", t, x), t) }
|
||||
if (meth == "sin") { return val(fp_call1("sin", t, x), t) }
|
||||
if (meth == "cos") { return val(fp_call1("cos", t, x), t) }
|
||||
if (meth == "exp") { return val(fp_call1("exp", t, x), t) }
|
||||
if (meth == "log") { return val(fp_call1("log", t, x), t) }
|
||||
if (meth == "tan") { return val(fp_call1("tan", t, x), t) }
|
||||
if (meth == "asin") { return val(fp_call1("asin", t, x), t) }
|
||||
if (meth == "acos") { return val(fp_call1("acos", t, x), t) }
|
||||
if (meth == "atan") { return val(fp_call1("atan", t, x), t) }
|
||||
if (meth == "floor") { return val(fp_call1("floor", t, x), t) }
|
||||
if (meth == "ceil") { return val(fp_call1("ceil", t, x), t) }
|
||||
if (meth == "round") { return val(fp_call1("round", t, x), t) }
|
||||
if (meth == "trunc") { return val(fp_intrinsic1("trunc", t, x), t) }
|
||||
if (meth == "sign") {
|
||||
let pos = emit_bind(`fcmp ogt {t} {x}, 0.0`)
|
||||
let neg = emit_bind(`fcmp olt {t} {x}, 0.0`)
|
||||
let lo = emit_bind(`select i1 {neg}, i32 -1, i32 0`)
|
||||
return val(emit_bind(`select i1 {pos}, i32 1, i32 {lo}`), "int")
|
||||
}
|
||||
if (meth == "deg_to_rad") { # x * (pi / 180), computed in t
|
||||
let k = emit_bind(`fdiv {t} {fp_lit_code("3.141592653589793", t)}, 180.0`)
|
||||
return val(emit_bind(`fmul {t} {x}, {k}`), t)
|
||||
}
|
||||
if (meth == "rad_to_deg") { # x * (180 / pi), computed in t
|
||||
let k = emit_bind(`fdiv {t} 180.0, {fp_lit_code("3.141592653589793", t)}`)
|
||||
return val(emit_bind(`fmul {t} {x}, {k}`), t)
|
||||
}
|
||||
if (meth == "min") or (meth == "max") { # the first unless the second is strictly smaller (larger)
|
||||
let y = fp_arg(e, 1, t, meth)
|
||||
return val(fp_pick(meth, t, x, y), t)
|
||||
}
|
||||
if (meth == "pow") { return val(fp_intrinsic2("pow", t, x, fp_arg(e, 1, t, meth)), t) }
|
||||
if (meth == "atan2") { return val(fp_call2("atan2", t, x, fp_arg(e, 1, t, meth)), t) }
|
||||
if (meth == "hypot") { return val(fp_call2("hypot", t, x, fp_arg(e, 1, t, meth)), t) }
|
||||
if (meth == "posmod") or (meth == "wrap") {
|
||||
# a result with the sign of the divisor: ((x % m) + m) % m
|
||||
let m = fp_arg(e, 1, t, meth)
|
||||
let r = emit_bind(`frem {t} {x}, {m}`)
|
||||
let s = emit_bind(`fadd {t} {r}, {m}`)
|
||||
return val(emit_bind(`frem {t} {s}, {m}`), t)
|
||||
}
|
||||
if (meth == "clamp") {
|
||||
let lo = fp_arg(e, 1, t, meth)
|
||||
let hi = fp_arg(e, 2, t, meth)
|
||||
let a = fp_pick("max", t, x, lo) # min(max(x, lo), hi)
|
||||
return val(fp_pick("min", t, a, hi), t)
|
||||
}
|
||||
if (meth == "lerp") { # lerp(a, b, t)
|
||||
let b = fp_arg(e, 1, t, meth)
|
||||
let k = fp_arg(e, 2, t, meth)
|
||||
let d = emit_bind(`fsub {t} {b}, {x}`)
|
||||
let dk = emit_bind(`fmul {t} {d}, {k}`)
|
||||
return val(emit_bind(`fadd {t} {x}, {dk}`), t)
|
||||
}
|
||||
if (meth == "inverse_lerp") { # inverse_lerp(a, b, v)
|
||||
let b = fp_arg(e, 1, t, meth)
|
||||
let v = fp_arg(e, 2, t, meth)
|
||||
let num = emit_bind(`fsub {t} {v}, {x}`)
|
||||
let den = emit_bind(`fsub {t} {b}, {x}`)
|
||||
return val(emit_bind(`fdiv {t} {num}, {den}`), t)
|
||||
}
|
||||
if (meth == "remap") { # remap(v, a0, a1, b0, b1)
|
||||
let a0 = fp_arg(e, 1, t, meth)
|
||||
let a1 = fp_arg(e, 2, t, meth)
|
||||
let b0 = fp_arg(e, 3, t, meth)
|
||||
let b1 = fp_arg(e, 4, t, meth)
|
||||
let num = emit_bind(`fsub {t} {x}, {a0}`)
|
||||
let den = emit_bind(`fsub {t} {a1}, {a0}`)
|
||||
let k = emit_bind(`fdiv {t} {num}, {den}`)
|
||||
let span = emit_bind(`fsub {t} {b1}, {b0}`)
|
||||
let off = emit_bind(`fmul {t} {span}, {k}`)
|
||||
return val(emit_bind(`fadd {t} {b0}, {off}`), t)
|
||||
}
|
||||
if (meth == "smoothstep") { # smoothstep(e0, e1, v)
|
||||
let e1 = fp_arg(e, 1, t, meth)
|
||||
let v = fp_arg(e, 2, t, meth)
|
||||
let num = emit_bind(`fsub {t} {v}, {x}`)
|
||||
let den = emit_bind(`fsub {t} {e1}, {x}`)
|
||||
let k0 = emit_bind(`fdiv {t} {num}, {den}`)
|
||||
let k1 = fp_pick("max", t, k0, "0.0")
|
||||
let k = fp_pick("min", t, k1, "1.0")
|
||||
let kk = emit_bind(`fmul {t} {k}, {k}`)
|
||||
let tk = emit_bind(`fmul {t} {k}, 2.0`)
|
||||
let three = emit_bind(`fsub {t} 3.0, {tk}`)
|
||||
return val(emit_bind(`fmul {t} {kk}, {three}`), t)
|
||||
}
|
||||
if (meth == "move_toward") { # move_toward(from, to, step)
|
||||
let to = fp_arg(e, 1, t, meth)
|
||||
let st = fp_arg(e, 2, t, meth)
|
||||
let d = emit_bind(`fsub {t} {to}, {x}`)
|
||||
let ad = fp_intrinsic1("fabs", t, d)
|
||||
let reach = emit_bind(`fcmp ole {t} {ad}, {st}`)
|
||||
let neg = emit_bind(`fcmp olt {t} {d}, 0.0`)
|
||||
let nst = emit_bind(`fneg {t} {st}`)
|
||||
let dir = emit_bind(`select i1 {neg}, {t} {nst}, {t} {st}`)
|
||||
let moved = emit_bind(`fadd {t} {x}, {dir}`)
|
||||
return val(emit_bind(`select i1 {reach}, {t} {to}, {t} {moved}`), t)
|
||||
}
|
||||
if (meth == "dist") or (meth == "dist2") { # dist(x1, y1, x2, y2)
|
||||
let y1 = fp_arg(e, 1, t, meth)
|
||||
let x2 = fp_arg(e, 2, t, meth)
|
||||
let y2 = fp_arg(e, 3, t, meth)
|
||||
let dx = emit_bind(`fsub {t} {x2}, {x}`)
|
||||
let dy = emit_bind(`fsub {t} {y2}, {y1}`)
|
||||
let dx2 = emit_bind(`fmul {t} {dx}, {dx}`)
|
||||
let dy2 = emit_bind(`fmul {t} {dy}, {dy}`)
|
||||
let s = emit_bind(`fadd {t} {dx2}, {dy2}`)
|
||||
if (meth == "dist2") { return val(s, t) }
|
||||
return val(fp_intrinsic1("sqrt", t, s), t)
|
||||
}
|
||||
perr(`Math.{meth} has no {t} form`)
|
||||
return val(x, t)
|
||||
}
|
||||
|
||||
# ---- pre-evaluated arguments ------------------------------------------------
|
||||
# A call that has to look at its first argument's type before choosing a
|
||||
# lowering evaluates it once and swaps in an E_PREVAL node, so the chosen path
|
||||
# does not evaluate it (and its side effects) a second time.
|
||||
var g_prevals: []Val
|
||||
|
||||
function preval_node(v: Val) -> Node {
|
||||
push(g_prevals, v)
|
||||
let n = node(E_PREVAL)
|
||||
n.ival = len(g_prevals) - 1
|
||||
return n
|
||||
}
|
||||
|
|
@ -26,7 +26,7 @@ function emit_str_const(s: pointer) -> pointer {
|
|||
|
||||
# the constant initializer for a global var: a literal, or 0/null
|
||||
function global_init(d: Node) -> pointer {
|
||||
if (d.a == null) { if (llty(d.ty) == "ptr") { return "null" }; return "0" }
|
||||
if (d.a == null) or is_fp(d.ty) { return zero_of(llty(d.ty)) } # a float's value is set by L_init_globals
|
||||
let e = d.a
|
||||
if e.kind == E_INT or e.kind == E_FLOAT or e.kind == E_BOOL { return itoa(e.ival) }
|
||||
if e.kind == E_UN and (e.s == ("-")) and e.a.kind == E_INT { return (("-") + itoa(e.a.ival)) }
|
||||
|
|
@ -40,6 +40,7 @@ function global_init(d: Node) -> pointer {
|
|||
# does a global's initializer need code at startup (anything global_init cannot fold)?
|
||||
function global_needs_init_code(d: Node) -> bool {
|
||||
if (d.a == null) { return false }
|
||||
if is_fp(d.ty) { return true }
|
||||
let e = d.a
|
||||
if e.kind == E_INT or e.kind == E_FLOAT or e.kind == E_BOOL or e.kind == E_NULL { return false }
|
||||
if e.kind == E_UN and (e.s == ("-")) and e.a.kind == E_INT { return false }
|
||||
|
|
@ -62,7 +63,8 @@ function emit_global_init_fn() -> void {
|
|||
let d = prog[i]
|
||||
if d.kind == N_VAR and global_needs_init_code(d) {
|
||||
let v = emit_expr(d.a)
|
||||
emit(" store "); emit(llty(d.ty)); emit(" "); emit(coerce_code(v, d.ty)); emit(", ptr @g_"); emit(d.s); emit("\n")
|
||||
let cv = coerce_code(v, d.ty) # any conversion is its own line, before the store
|
||||
emit(" store "); emit(llty(d.ty)); emit(" "); emit(cv); emit(", ptr @g_"); emit(d.s); emit("\n")
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,11 +36,10 @@ function emit_new_struct(name: pointer, rec: Node) -> Val {
|
|||
emit(" "); emit(addr); emit(" = getelementptr inbounds "); emit(lty)
|
||||
emit(", ptr "); emit(obj); emit(", i32 0, i32 "); emit(itoa(f)); emit("\n")
|
||||
let lt = llty(fd.ty)
|
||||
var v = "0"
|
||||
if (lt == "ptr") { v = "null" }
|
||||
if (fd.a != null) { let dv = emit_expr(fd.a); v = dv.code }
|
||||
var v = zero_of(lt)
|
||||
if (fd.a != null) { let dv = emit_expr(fd.a); v = coerce_code(dv, fd.ty) }
|
||||
let ov = rec_field(rec, fd.s) # explicit override wins over the default
|
||||
if (ov != null) { let dv = emit_expr(ov); v = dv.code }
|
||||
if (ov != null) { let dv = emit_expr(ov); v = coerce_code(dv, fd.ty) }
|
||||
emit(" store "); emit(lt); emit(" "); emit(v); emit(", ptr "); emit(addr); emit("\n")
|
||||
f += 1
|
||||
}
|
||||
|
|
|
|||
|
|
@ -316,9 +316,7 @@ function emit_stmt(st: Node) -> void {
|
|||
let slot = emit_alloca(llty(ty)); store_at(llty(ty), v0.code, slot); loc_push(st.s, slot, ty); loc_set_mut(st.ival); return }
|
||||
let slot = emit_alloca(llty(ty))
|
||||
if (st.a == null) {
|
||||
var z = "0"
|
||||
if (llty(ty) == "ptr") { z = "null" }
|
||||
store_at(llty(ty), z, slot)
|
||||
store_at(llty(ty), zero_of(llty(ty)), slot)
|
||||
} else { let v = emit_expr(st.a); store_at(llty(ty), coerce_code(v, ty), slot) }
|
||||
loc_push(st.s, slot, ty)
|
||||
loc_set_mut(st.ival)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
# called only when the runtime defines them.
|
||||
|
||||
function emit_system_fn(sys: Node) -> void {
|
||||
det_enter(sys.s)
|
||||
g_cur_scene = sys.c # scene owning this handler (null if global) — for `become`
|
||||
ll_t = 0; ll_lbl = 0; g_term = false; loc_reset(); nloop = 0; nself = 0
|
||||
ret_ty = "void"
|
||||
|
|
@ -19,6 +20,7 @@ function emit_system_fn(sys: Node) -> void {
|
|||
emit(buf_str(falloc))
|
||||
emit(buf_str(fbody))
|
||||
emit("}\n\n")
|
||||
g_det_ctx = ""
|
||||
}
|
||||
|
||||
# one enable-gated call to @sys_<d.s> (skipped while the handler is disabled).
|
||||
|
|
|
|||
|
|
@ -26,9 +26,8 @@ function emit_init_component(e: pointer, comp: pointer, rec: Node) -> void {
|
|||
let addr = nreg()
|
||||
emit(" "); emit(addr); emit(" = getelementptr inbounds %Cmp_"); emit(comp); emit(", ptr "); emit(slot); emit(", i32 0, i32 "); emit(itoa(f)); emit("\n")
|
||||
let lt = llty(fd.ty)
|
||||
var v = "0"
|
||||
if (lt == "ptr") { v = "null" }
|
||||
if (fd.a != null) { let dv = emit_expr(fd.a); v = dv.code }
|
||||
var v = zero_of(lt)
|
||||
if (fd.a != null) { let dv = emit_expr(fd.a); v = coerce_code(dv, fd.ty) }
|
||||
emit(" store "); emit(lt); emit(" "); emit(v); emit(", ptr "); emit(addr); emit("\n")
|
||||
f += 1
|
||||
}
|
||||
|
|
@ -42,7 +41,9 @@ function emit_init_component(e: pointer, comp: pointer, rec: Node) -> void {
|
|||
let addr = nreg()
|
||||
emit(" "); emit(addr); emit(" = getelementptr inbounds %Cmp_"); emit(comp); emit(", ptr "); emit(slot); emit(", i32 0, i32 "); emit(itoa(fidx)); emit("\n")
|
||||
let dv = emit_expr(fi.a)
|
||||
emit(" store "); emit(llty(field_type(c, fi.s))); emit(" "); emit(dv.code); emit(", ptr "); emit(addr); emit("\n")
|
||||
let fty = field_type(c, fi.s)
|
||||
let dcode = coerce_code(dv, fty)
|
||||
emit(" store "); emit(llty(fty)); emit(" "); emit(dcode); emit(", ptr "); emit(addr); emit("\n")
|
||||
}
|
||||
j += 1
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,16 @@ function is_math_builtin(name: pointer) -> bool {
|
|||
}
|
||||
|
||||
function emit_math_builtin(name: pointer, e: Node) -> Val {
|
||||
if len(e.kids) == 0 { return emit_math_builtin_fixed(name, e) }
|
||||
let orig = e.kids[0]
|
||||
let fpv = math_fp_route(name, e)
|
||||
if (fpv != null) { return fpv }
|
||||
let r = emit_math_builtin_fixed(name, e)
|
||||
e.kids[0] = orig # the node may be emitted again elsewhere
|
||||
return r
|
||||
}
|
||||
|
||||
function emit_math_builtin_fixed(name: pointer, e: Node) -> Val {
|
||||
if (name == "abs") {
|
||||
let a = emit_expr(e.kids[0])
|
||||
let c = emit_bind(`icmp slt i32 {a.code}, 0`)
|
||||
|
|
@ -79,9 +89,31 @@ function is_math_ns(meth: pointer) -> bool {
|
|||
return false
|
||||
}
|
||||
|
||||
# a float/double call goes to emit_fp_math; otherwise the evaluated first
|
||||
# argument is left in place (E_PREVAL) for the fixed-point path. null = fixed.
|
||||
function math_fp_route(meth: pointer, e: Node) -> Val {
|
||||
if len(e.kids) == 0 { return null }
|
||||
if (e.kids[0].kind == E_PREVAL) { return null }
|
||||
let first = emit_expr(e.kids[0])
|
||||
let t = fp_math_type(first, e)
|
||||
if not (t == "") { return emit_fp_math(meth, t, first, e) }
|
||||
e.kids[0] = preval_node(first)
|
||||
return null
|
||||
}
|
||||
|
||||
function emit_math_ns(meth: pointer, e: Node) -> Val {
|
||||
if len(e.kids) == 0 { return emit_math_ns_fixed(meth, e) }
|
||||
let orig = e.kids[0]
|
||||
let fpv = math_fp_route(meth, e)
|
||||
if (fpv != null) { return fpv }
|
||||
let r = emit_math_ns_fixed(meth, e)
|
||||
e.kids[0] = orig # the node may be emitted again elsewhere
|
||||
return r
|
||||
}
|
||||
|
||||
function emit_math_ns_fixed(meth: pointer, e: Node) -> Val {
|
||||
if (meth == "min") or (meth == "max") or (meth == "abs") or (meth == "clamp") {
|
||||
return emit_math_builtin(meth, e)
|
||||
return emit_math_builtin_fixed(meth, e)
|
||||
}
|
||||
if (meth == "sign") { # sign(x) -> -1 / 0 / 1 (int)
|
||||
let a = emit_expr(e.kids[0])
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue